Why vue reactive variable update _value property instead of value property? - javascript

So I am learning vue 3 with composition API and I want to change the .value property of a variable 'filteredProjects' from axios response like so...
const filteredProjects = ref(null)
onMounted(async () => {
await api.get("/project").then(response => filteredProjects.value = response.data);
})
console.log(filteredProjects.value)
I tried console.log(filteredProjects.value) but it returned null, so I checked out the variable without .value property console.log(filteredProjects) and find out that the response data from API is being set on the _value property instead of .value. This is the console result
RefImpl {__v_isShallow: false, dep: undefined, __v_isRef: true, _rawValue: null, _value: null}
dep: Set(1) {ReactiveEffect}
__v_isRef: true
__v_isShallow: false
_rawValue: {data: Array(13), message: 'successfully get data'}
_value: Proxy {data: Array(13), message: 'successfully get data'}
value: (...)
[[Prototype]]: Object

You're doing an asynchronous call inside the mounted hook which will run after console.log(filteredProjects.value), you could watch the filteredProjects or add async to the setup hook and await the response:
import {ref,watch,onMounted} from 'vue';
const filteredProjects = ref(null)
onMounted(async () => {
await api.get("/project").then(response => filteredProjects.value = response.data);
})
watch(filteredProjects ,()=>{
console.log(filteredProjects.value)
})
or with script setup, no need to add async anywhere :
<script setup>
import {ref,watch} from 'vue';
const filteredProjects = ref(null)
const res = await api.get("/project").
filteredProjects.value = res.data;
console.log(filteredProjects.value)
</script>

Related

How to iterate useQuery with payload inside or outside onMounted

i am trying to iterate over a query that requires as variable.
The first thing i tried was doing it inside onMounted like this:
onMounted(async () => {
arrayOfObjects.value.forEach(async (object: ObjectType) => {
const { data } = await useQuery<QueryResponse>({
variables: { id: object.id },
query: QUERY,
});
});
});
Doing this will give the following error:
Error: use* functions may only be called during the 'setup()' or other lifecycle hooks.
So then i tried to save the id in a ref() and set the query outside onMounted like this:
const id = ref<number>();
const { executeQuery: getObject } = await useQuery<QueryResponse>({
variables: { id: id.value },
query: QUERY,
pause: true,
});
onMounted(async () => {
arrayOfObjects.value.forEach(async (object: ObjectType) => {
id.value = object.id;
const objectValue = await getObject();
});
});
The problem here is the despite pause being set to true it will try to execute the query while the ref id is still undefined so the response is the following error:
message: "Variable "$id" of required type "ID!" was not provided."
I know the given ID is correct because i tried using the query with a hardcoded ID.
Any ideas what could help?

React SWR - how to know that updating (mutating) is running?

Im mostly using SWR to get data, however I have a situation that I need to update data. The problem is, I need an indicator that this request is ongoing, something like isLoading flag. In the docs there's a suggestion to use
const isLoading = !data && !error;
But of course when updating (mutating) the data still exists so this flag is always false. The same with isValidating flag:
const { isValidating } = useSWR(...);
This flag does NOT change when mutation is ongoing but only when its done and GET request has started.
Question
Is there a way to know if my PUT is loading? Note: I dont want to use any fields in state because it won't be shared just like SWR data is. Maybe Im doing something wrong with my SWR code?
const fetcher = (url, payload) => axios.post(url, payload).then((res) => res);
// ^^^^^ its POST but it only fetches data
const updater = (url, payload) => axios.put(url, payload).then((res) => res);
// ^^^^^ this one UPDATES the data
const useHook = () => {
const { data, error, mutate, isValidating } = useSWR([getURL, payload], fetcher);
const { mutate: update } = useSWRConfig();
const updateData = () => {
update(getURL, updater(putURL, payload)); // update data
mutate(); // refetch data after update
};
return {
data,
updateData,
isValidating, // true only when fetching data
isLoading: !data && !error, // true only when fetching data
}
Edit: for any other who reading this and facing the same issue... didnt find any solution for it so switched to react-query. Bye SWR
const { mutate: update } = useSWRConfig();
const updateData = () => {
// this will return promise
update(getURL, updater(putURL, payload)); // update data
mutate(); // refetch data after update
};
By using react-toastify npm module to show the user status.
// first wrap your app with: import { ToastContainer } from "react-toastify";
import { toast } from "react-toastify";
const promise=update(getURL, updater(putURL, payload))
await toast.promise(promise, {
pending: "Mutating data",
success: "muttation is successfull",
error: "Mutation failed",
});
const markSourceMiddleware = (useSWRNext) => (key, fetcher, config) => {
const nextFetcher = (...params) =>
fetcher(...params).then((response) => ({
source: "query",
response,
}));
const swr = useSWRNext(key, nextFetcher, config);
return swr;
};
const useHook = () => {
const {
data: { source, response },
mutate,
} = useSWR(key, fetcher, { use: [markSourceMiddleware] });
const update = mutate(
updateRequest().then((res) => ({
source: "update",
response,
})),
{
optimisticData: {
source: "update",
response,
},
}
);
return {
update,
updating: source === "update",
};
};
Hmm based on that:
https://swr.vercel.app/docs/conditional-fetching
It should work that the "is loading" state is when your updater is evaluates to "falsy" value.
REMAINDER! I don't know react swr just looked into docs - to much time at the end of the weekend :D
At least I hope I'll start discussion :D

Is there a way to update a returned value from a JS function?

I am making a chat app using Firebase and RN. In my firebase code I have a function like this:
//all values are declared before, db is from firebase config which i do not wish to share
import "firebase";
async function getPublic(dba = db) {
const messages = onSnapshot(doc( /*collection name ->*/"public", dba), db => db.docs())
return messages;
}
Is there a way to update the returned value or something similar to that?
Instead of naming your function getPublic, consider instead usePublic. Or even better, generalize it so you can use different paths.
But first, we need to look at the definition of onSnapshot() (a CollectionReference extends from Query):
export declare function onSnapshot<T>(query: Query<T>, observer: {
next?: (snapshot: QuerySnapshot<T>) => void;
error?: (error: FirestoreError) => void;
complete?: () => void;
}): Unsubscribe;
As you can see here, the messages aren't returned from this function, but an Unsubscribe function is (a () => void). So to update a messages array, you'll need to use useState and because you are using realtime listeners, you should use useEffect to manage the listener lifecycle. You also should handle the intermediate states such as loading, errored and fetched data. This results in:
import { useEffect, useState } from 'react';
import { getFirestore, collection, onSnapshot } from "firebase/firestore";
function useMessageFeed(feed = "public", firestore = getFirestore()) { // use default firestore instance unless told otherwise
// set up somewhere to store the data
const [ messagesInfo, setMessagesInfo ] = useState(/* default messagesInfo: */ {
status: "loading",
messages: null,
error: null
});
// attach and manage the listener
useEffect(() => {
const unsubscribe = onSnapshot( // unsubscribe is a () => void
collection(/* firestore instance: */ firestore, /* collection path: */ feed),
{
next: querySnapshot => setMessagesInfo({
status: "loaded",
messages: querySnapshot.docs(), // consider querySnapshot.docs().map(doc => ({ id: doc.id, ...doc.data() }))
error: null
}),
error: err => setMessagesInfo({
status: "error",
messages: null,
error: err
})
}
);
return unsubscribe;
}, [firestore, feed]); // <-- if these change, destroy and recreate the listener
return messagesInfo; // return the data to the caller
}
Elsewhere in your code, you would use it like this:
const SomeComponent = (props) => {
const { status, messages, error: messagesError } = useMessageFeed("public");
switch (status) {
case "loading":
return null; // hides component
case "error":
return (
<div class="error">
Failed to retrieve data: {messagesError.message}
</div>
);
}
// render messages
return (
/* ... */
);
}

I receive an error when attempting to .map through an array from an api

I currently am trying to iterate through an array that I got from an api.
My current code is :
displayEmailList = () => {
let emails = [...this.state.info.emails]
return emails.map(email => {
console.log(email)
})
}
This is my state and async function :
state = {
info: '',
domain: 'homeadvisor.com'
};
async componentDidMount() {
let info = await axios.get(
`https://api.hunter.io/v2/domain-search?domain=${this.state
.domain}&api_key=76056a7300959044150346f9d8dd3c5d6faef844`
);
this.setState({
info: info.data.data
});
}
The Error message I receive is:
TypeError: this.state.info.emails is not iterable
However if I console.log(this.state.info)
I can clearly see that I have an array of emails
Your initial value of info in your state is an empty string.
Initialise your state like this:
state = {
info: {email: []},
domain: 'homeadvisor.com'
}

How can I mock axios API calls? with using jest

Hi I'm testing my vuex action async function which is calling api via axios, but I have some problem that it show error like this "
TypeError: Cannot destructure property data of 'undefined' or 'null'.
35 | commit('storeSearchValue', name);
36 | const url = process.env.VUE_APP_URL_API_News + '/news' + '?q=' + name;
> 37 | const { data } = await axios.get(url);"
my vue js code is
async updateSearchValue({ commit }, name) {
commit('storeSearchValue', name);
const url = process.env.VUE_APP_URL_API_News + '/news' + '?q=' + name;
const { data } = await axios.get(url);
commit('storeNewsData', data.result);
},
and this is test file,
import actions from '#/store/modules/data/data-actions.js'
import VueRouter from 'vue-router';
import axios from 'axios';
import {
createLocalVue
} from '#vue/test-utils';
const localVue = createLocalVue();
localVue.use(VueRouter);
jest.mock('axios');
describe('', () => {
test('updateSearchValue', async () => {
const commit = jest.fn()
const name = jest.fn()
await actions.updateSearchValue({
commit,
name
})
expect(commit).toHaveBeenCalledWith('updateSearchValue', name)
})
})
I'm working with jest and TS and trying to do:
axios.get.mockReturnValue...
or:
axios.get.mockImplementationOnce...
returned the following error:
TypeError: mockedAxios.get.mockImplementationOnce is not a function
The thing that finally did the trick for me was:
import axios from 'axios';
jest.mock('axios');
axios.get = jest.fn()
.mockImplementationOnce(() => Promise.resolve({ data: 'mock data' }));
You have used jest.mock('axios') which is automatically generating mock for module and it will create jest.fn() for axios.get, but it will return undefined unless you tell it otherwise
Since you're expecting it to return a resolved promise with object with data property you can use:
axios.get.mockReturnValue(Promise.resolve({
data: 'mock data'
});
or the short-hand:
axios.get.mockResolvedValue({ data: 'mock data' });
Also check this answer

Categories