Provide Inject not working properly in vue 3 composition API - vue.js

I am working with Vue 3 composition api and am retrieving weather data via async/await fetch and I get a 200 response and the data in the request within the Chrome Dev Tools.
In the component receiving the data and making the call I have a provide method and then I am injecting the data into another output component. The issue is in the inject component. The value for the injected variable is always null and does not update in the Vue Dev Tools so my data is never output to the screen. I went through the docs and the code is pretty much the same but I can't get it to work. Can anyone see an obvious issue?
Receiving Component
setup () {
async function getCurrentWeather () {
const response = await fetch(`${baseWeatherApiUrl}q=${userInput.value}`);
userInput.value = null;
return weatherData.value = await response.json();
}
const returnedWeatherData = reactive(weatherData);
provide('returnedWeatherData', returnedWeatherData);
return {
getCurrentWeather,
userInput,
weatherData
}
}
output component
setup () {
//Provide default of empty object in case no results exist
const weatherData = inject('returnedWeatherData');
console.log(weatherData) //No output even when making a new request to the weather api
return {
weatherData
}
}
As a separate test I tried to provide/inject hardcoded values found in the docs but still geolocation when injected remains null.
provide('geolocation', {
longitude: 90,
latitude: 135
})
const userGeolocation = inject('geolocation')
console.log(userGeolocation) // Nothing logged
return {
weatherData,
userGeolocation
}

In my case it was importing inject from "#vue/runtime-core" instead of "vue".
Of course provide was imported from "vue".
Just leaving here, maybe it's gonna save someone an hour.

The provide-ed argument should be the ref itself (not wrapped in a reactive()):
// Parent.vue
export default {
setup () {
const weatherData = ref()
// ❌
// const returnedWeatherData = reactive(weatherData);
// provide('returnedWeatherData', returnedWeatherData);
// ✅
provide('returnedWeatherData', weatherData);
}
}
And the child component's console.log() in setup() does not automatically get invoked again. You should wrap that call with watchEffect() so that it does get called upon change to the ref:
// Child.vue
import { inject, watchEffect } from 'vue'
export default {
setup () {
const weatherData = inject('returnedWeatherData')
// ❌
//console.log('new weatherData', weatherData.value)
// ✅
watchEffect(() => {
console.log('new weatherData', weatherData.value)
})
}
}
demo

Related

Redefine $fetch in nuxt3 with global onRequest handler

Is it possible to use global onRequest handler to $fetch with Nuxt3, to add specific data on each request?
With nuxt2 and axios it was simple
/plugins/axios.js
export default function ({ $axios, store, req }) {
$axios.onRequest((config) => {
if (config.data) {
config.data.test = '123';
} else {
config.data = { test: '123' };
}
return config;
});
}
But how achieve same goal on Nuxt3 and $fetch?
Ok, so Nuxt3 $fetch documentation says:
Nuxt uses ofetch to expose globally the $fetch helper...
When we jump into ofetch documentation we can see the Interceptors section. This gives us some options to do what you are trying to achieve. My suggestion is this:
Create a http composable (or anyother name you wish):
// composables/use-http.js
const opts = {
async onRequest({ request, options }) {
// Add your specific data here
options.query = { t: '1234' }
options.headers = { 'Authorization': 'my_token' }
}
}
export default () => $fetch.create(opts)
And here we are making usage of the onRequest interceptor from ofetch
onRequest is called as soon as ofetch is being called, allowing to modify options or just do simple logging.
There you can add any data you want, if you need you can create the logic to pass parameters to this composable and so on...
Now, to actually fetch the data (use the composable):
const http = useHttp() // useHttp is auto-imported
const data = await http('/url') // will trigger the interceptor

Vue JS composition API can't access array

I am currently trying to learn Vue JS without ever having encountered Javascript.
All the brackets, arrows, etc. are driving me crazy.
With the Composition API, I come across a question that I can't successfully google.
That's working:
setup() {
const store = useStore ();
const packagingdata = ref ([]);
const loadpackagingdata = async () => {
await store.dispatch (Actions.LOADPACKAGING_LIST, 10);
packagingdata.value = store.getters.getPackagingData;
return {
packagingdata,
}
I can access {{packagingdata}}. That contains an array.
{{Packagingdata.products}}
does work
but {{packagingdata.products [0]}} doesn't work.
But when I add it to the setup () like this:
setup() {
const store = useStore ();
const packagingdata = ref ([]);
const getProducts = ref ([]);
const loadpackagingdata = async () => {
await store.dispatch (Actions.LOADPACKAGING_LIST, 10);
packagingdata.value = store.getters.getPackagingData;
getProducts.value = store.getters.getProducts;
};
return {
packagingdata,
getProducts
}
then {{ getProducts }} returns what I wanted even if the getter function only is like this:
get getAddress() {
return this.packagingdata["products"][0];
}
What is happening there?
What am I doing wrong? I would prefer to not create a ref() and getterfunction for every computed value.
Whats the solution with computed?
best regards
I found two methods to avoid the error.
add a v-if="mygetter.length" in the template
check in the getter whether the variable is set and otherwise return false
Both of these prevent an error already occurring when the page is loaded that [0] cannot be found

Computed params with skip/enable

I was getting annoyed that some of my apollo requests were working and some were not. The ones that don't seem to work are requests with computed params.
Here is an example of one that does work:
import { computed } from "#vue/composition-api";
import * as getCategoryBySlug from "#graphql/api/query.category.gql";
import { useGraphQuery } from "./graph-query";
export function useGetCategory(context) {
const params = computed(() => {
const slug = context.root.$route.params.categorySlug;
if (!slug) return;
return { slug };
});
const { response, error, loading } = useGraphQuery(
params,
getCategoryBySlug,
(data) => data.categoryBySlug
);
return { category: response, categoryError: error, categoryLoading: loading };
}
As I am computing my params on the categorySlug, it is available on the route, so it should never be null/undefined.
My useGraphQuery method looks like this:
import { useQuery, useResult } from "#vue/apollo-composable";
export function useGraphQuery(params, gql, pathFn, clientId = "apiClient") {
// if (!params?.value)
// return {
// response: ref(undefined),
// loading: ref(false),
// error: ref(undefined),
// query: ref(undefined),
// };
// TODO: figure our a way to skip the call if the parameters are null
const { result, loading, error, query, fetchMore } = useQuery(gql, params, {
clientId,
//enabled: !!params?.value,
});
const response = useResult(result, null, pathFn);
return { response, loading, error, query, fetchMore };
}
As you can see, I am having an issue because I can't skip and enabled doesn't seem to work as a suitable workaround (for skip).
I tried to return a reference if the parameters are null/undefined, but this never tried to execute the query if the computed params became available.
So my question is how can I skip the request or wait until the params are available?
You should consider changing the flow that calls this method, so it would be called only when the params are defined, instead of trying to skip it when not ready and try to retrigger it again from within the method. Most of the time this will make the code clearer, and also make it more resource-efficient as it won't make unneeded method calls.
If you depend on user input try to validate the input is ready before calling this method.
You can also add a watcher on the params that will trigger the flow when they change, and check in the watcher that all the relevant values are defined before calling the method.
Of course, if you can use computed variables it is better than using watchers in most cases, but in some cases, it can help (mostly when the variable is calculated by an async function, for example, the use of apollo request).

Vuex populate data from API call at the start

apologies for the simple question, I'm really new to Vue/Nuxt/Vuex.
I am currently having a vuex store, I wish to be able to populate the list with an API call at the beginning (so that I would be able to access it on all pages of my app directly from the store vs instantiating it within a component).
store.js
export const state = () => ({
list: [],
})
export const mutations = {
set(state, testArray) {
state.list = testArray
}
}
export const getters = {
getArray: state => {
return state.list
},
}
I essentially want to pre-populate state.list so that my components can call the data directly from vuex store. This would look something like that
db.collection("test").doc("test").get().then(doc=> {
let data = doc.data();
let array = data.array; // get array from API call
setListAsArray(); // put the array result into the list
});
I am looking for where to put this code (I assume inside store.js) and how to go about chaining this with the export. Thanks a lot in advance and sorry if it's a simple question.
(Edit) Context:
So why I am looking for this solution was because I used to commit the data (from the API call) to the store inside one of my Vue components - index.vue from my main page. This means that my data was initialized on this component, and if i go straight to another route, my data will not be available there.
This means: http://localhost:3000/ will have the data, if I routed to http://localhost:3000/test it will also have the data, BUT if i directly went straight to http://localhost:3000/test from a new window it will NOT have the data.
EDIT2:
Tried the suggestion with nuxtServerInit
Updated store.js
export const state = () => ({
list: [],
})
export const mutations = {
set(state, dealArray) {
state.list = dealArray
}
}
export const getters = {
allDeals: state => {
return state.list
},
}
export const actions = {
async nuxtServerInit({ commit }, { req }) {
// fetch your backend
const db = require("~/plugins/firebase.js").db;
let doc = await db.collection("test").doc("test").get();
let data = doc.data();
console.log("deals_array: ", data.deals_array); // nothing logged
commit('set', data.deals_array); // doesn't work
commit('deals/set', data.deals_array); // doesn't work
}
}
Tried actions with nuxtServerInit, but when logging store in another component it is an empty array. I tried to log the store in another component (while trying to access it), I got the following:
store.state: {
deals: {
list: []
}
}
I would suggest to either:
calling the fetch method in the default.vue layout or any page
use the nuxtServerInit action inside the store directly
fetch method
You can use the fetch method either in the default.vue layout where it is called every time for each page that is using the layout. Or define the fetch method on separate pages if you want to load specific data for individual pages.
<script>
export default {
data () {
return {}
},
async fetch ({store}) {
// fetch your backend
var list = await $axios.get("http://localhost:8000/list");
store.commit("set", list);
},
}
</script>
You can read more regarding the fetch method in the nuxtjs docs here
use the nuxtServerInit action inside the store directly
In your store.js add a new action:
import axios from 'axios';
actions: {
nuxtServerInit ({ commit }, { req }) {
// fetch your backend
var list = await axios.get("http://localhost:8000/list");
commit('set', list);
}
}
}
You can read more regarding the fetch method in the nuxtjs docs here
Hope this helps :)

Nuxt Fetch Doesn't Update on First Load

I'm having the following issue and hope someone could help me on it:
Fetch is not working on the first load (nor on reloads). It only works when on the client-side (when I move between routes).
I've read that watchQuery could help but didn't understand why and how to use it.
<script>
export default {
async fetch() {
const userId = await this.$nuxt.context.store.state.auth.authUser.userId
await this.$store.dispatch('case/fetchMyCases', userId.uid)
await this.$store.dispatch('case/fetchMyPendingCases', userId.uid)
...
It doesn't work even if I import and use firebase/auth directly.
<script>
import * as firebase from 'firebase/app'
import 'firebase/auth'
export default {
async fetch() {
const userId = await firebase.auth().currentUser
await this.$store.dispatch('case/fetchMyCases', userId.uid)
await this.$store.dispatch('case/fetchMyPendingCases', userId.uid)
...
Does anyone have any tips for it? I'd really appreciate it.
Thanks!
After literally 3 days searching/testing, I finally found out why I was having this issue.
The problem was that I simply put async/await for fetch but didn't put async/await for the actions itself. Therefore, my getter (in computed) was getting the store state before the dispatches have been finished.
Thanks, everyone!
Warning: You don't have access of the component instance through this inside fetch because it is called before initiating the component (server-side).
async fetch({ store }) {
await store.dispatch('case/fetchMyCases')
await store.dispatch('case/fetchMyPendingCases')
}
If you need parameter:
async fetch({ store, params }) {
await store.dispatch('case/fetchMyCases', params.uid)
await store.dispatch('case/fetchMyPendingCases', params.uid)
}
I gave an example of id. The name of the parameter depends on the name of your page.
_id => params.id
_uid => params.uid
_slug => params.slug
...
Yes, You must put async/await on actions.
async automatically returns a promise
If you don't need the value, in this case, don't anything return.
export const Actions = {
async fetchUsers() {
// It will return automatically promise
await this.$axios.get('API')
}
}
// If you need returne value
// First way
export const Actions = {
async fetchUsers() {
// It will return promise and value
return await this.$axios.get('API')
}
}
// Second way
export const Actions = {
async fetchUsers() {
// It will return promise and value
const response = await this.$axios.get('API')
return response;
}
}