const getQueryService = () => {
return {
login: async (id): Promise<AuthLoginGoogleResponse> => {
try {
const result = await authApi.loginGooglePost({
idToken: {
id_token: id,
},
});
return result;
} catch (error) {
console.error("Google Login Fail", error);
}
},
};
};
// Mutation is only for updating and creating and deleting
const getMutationService = () => {
return {};
};
const useGoogleLogin = () => {
const queryClient = useQueryClient();
const queryService = getQueryService();
// const { data, isLoading } = useQuery('auth', queryService.login)
const mutationService = getMutationService();
const fetchLoginData = async (
tokenId
): Promise<AuthLoginGoogleResponse | void> => {
return await queryClient.prefetchQuery("auth", async() => {
return await queryService.login(tokenId);
});
};
return fetchLoginData;
};
I am sending token.Id to API using Post request and I am calling it from component however when I run debugger, preFetchquery is not returning the value retuned from result in getqueryservice function.
Is there a reason why preFetchQuery is not returning the return value from getQueryService.login?
because that's what prefetching does. According to the docs (emphasis mine):
prefetchQuery is an asynchronous method that can be used to prefetch a query before it is needed or rendered with useQuery and friends. The method works the same as fetchQuery except that it will not throw or return any data.
So prefetchQuery just puts data in the cache so that it can be picked up later by useQuery, hence the name: pre-fetch.
If you wan to get data returned, you can use queryClient.fetchQuery instead - but you'd also need to handle errors in case the fetch fails.
To be honest, I'm not sure why you are trying to achieve though. Judging from the code, it looks like you're trying to execute a query when the user wants to login. Please keep in mind that this is not what queries are for. Logging someone in is a prime example for a mutation.
Related
So I'm trying to use React Query with Firestore and usually this works pretty fine but recently I have been unable to make a function that returns a value.
const fetchProduct = async () => {
const querySnapshot = await getDocs(collection(db, "notes"));
const arr = [];
querySnapshot.forEach((doc) => {
setNotes(doc.data())
}).catch((error) => {
console.log(error);
return null;
});
return notes
}
I'm later trying to use this function with React Query like this -
const { isLoading, isError, data, error } = useQuery(['todos'], fetchProduct)
However the value of the {data} always return to undefined but however once the fetchProduct() function is called manually it all works.
Is there anything I'm missing or doing wrong?
Setting the state is an asynchronous operation in React (see docs), so the value of notes isn't set yet by the time your return notes runs.
If you want to return the value synchronously:
return querySnapshot.docs.map((doc) => doc.data());
More typically though, you'll want to put the code that depends on that return value into its own useEffect hook that depends on the notes state.
Also see:
The useState set method is not reflecting a change immediately
Why does calling react setState method not mutate the state immediately?
Is useState synchronous?
It should be like this, you should not set inside the foreach function
const fetchProduct = async () => {
const querySnapshot = await getDocs(collection(db, "notes"));
const notes = [];
querySnapshot.forEach((note) => {
notes.push({ ...note.data(), id: note.id })
}).catch((error) => {
console.log(error);
return null;
});
return notes;
}
// in the place of the calling the function
const notes = await fetchProduct();
setNotes(notes);
Note: don't use variable name as doc in foreach, instead use some other variable name like since doc is built in function of firebase you might have imported it
I'm lost. I've tried almost all I know. In my other component, similar process works fine, but in this one there is something obviously wrong implemented.
I have a Context Provider, and set two values to share, a function to call an Api and retrieve a list of contacts (getContactsList), and a variable where I put that list of contacts (contactsList).
I call getContactsList in useEffect. Later, I can use contactsList variable, but is always an empty array. I know that the problem is related to Promises and async functions maybe, but I can't find the mistake in my code.
I left here a copy of the components, starting for the view component that has the problem:
Detail Component:
function ContactDetail() {
const { getContactsList, contactsList } = useContext(ContactsContext);
const { id } = useParams();
useEffect(() => { getContactsList().catch(null) }, []);
const contact = contactsList?.filter(c => {
return (Number(c.id) === Number(id))
});
return (
<div>
{contact? "contact finded" : "contact not finded"}
</div>
);
}
ApiCall
async function apiCall (
url,
method = "get",
body,
headers)
{
try {
const response = await fetch(url, {
method,
body,
headers
});
const r = await response.json();
return r;
}
catch (err) {
Promise.reject(err);
}
}
Provider
function ContactsProvider({ children }) {
const [ contactsList,setContactsList ] = useState([]);
const getContactsList = async () => {
try {
const contactsResult = await apiCall("https://fakeurl.com");
setContactsList(contactsResult);
}
catch(err) {
setContactsList([]);
}};
return (
<ContactsContext.Provider value={{getContactsList, contactsList}}>
{children}
</ContactsContext.Provider>
);
}
Also, the code is in my GitHub: https://github.com/thenablyn/local-search-browser.git
This code doesn't seem to be in your repo, but I've tested what you've written and it seems to work, is there a chance the id coming from useParams isn't matching any of the contacts when you're filtering?
The error was that I forgot to get the first element when I did the array.filter() method. It's a Jr mistake.
The code in that assignment has to be like this:
const [contact] = contactsList.filter(...); //this get the first element and assign to constant "contact"
I am writing a CRUD app with React Query and I created some custom hooks as described here: https://react-query.tanstack.com/examples/custom-hooks
In the docs I see that there are basically two ways to update the cache after a mutation:
Query invalidation (https://react-query.tanstack.com/guides/query-invalidation)
onSuccess: () => {
queryClient.invalidateQueries("posts");
}
Updating the cache manually (https://react-query.tanstack.com/guides/invalidations-from-mutations)
// Update post example
// I get the updated post data for onSuccess
onSuccess: (data) => {
queryClient.setQueryData("posts", (oldData) => {
const index = oldData.findIndex((post) => post.id === data.id);
if (index > -1) {
return [
...oldData.slice(0, index),
data,
...oldData.slice(index + 1),
];
}
});
},
I understand that manual update has the advantage of not doing an extra call for fetching the 'posts', but I wonder if there is any advantage of invalidating cache over the manual update. For example:
import { useMutation, useQueryClient } from "react-query";
const { API_URL } = process.env;
const createPost = async (payload) => {
const options = {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
};
if (API_URL) {
try {
const response = await fetch(API_URL, options);
if (!response.ok) {
throw new Error(response.statusText);
}
return response.json();
} catch (error) {
throw new Error(error);
}
} else {
throw new Error("No api url is set");
}
};
export default function useCreatePost() {
const queryClient = useQueryClient();
return useMutation((payload) => createPost(payload), {
// DOES INVALIDATING HAVE ANY ADVANTAGE OVER MANUAL UPDATE IN THIS CASE?
// onSuccess: () => {
// queryClient.invalidateQueries("posts");
// },
onSuccess: (data) => {
queryClient.setQueryData("posts", (oldData) => {
return [...oldData, data];
});
},
});
}
Thanks for your time!
As you state it yourself, the only advantage is that you don't waste another network call to update data we already have.
Here we have a create and delete example.
import { useMutation, useQueryClient } from 'react-query'
const queryClient = useQueryClient()
// createPost(post: PostT) {
// const { data } = await http.post<{ post: PostT >('/posts', { post });
// return data.post;
// }
const mutation = useMutation(createPost, {
onSuccess: (post) => {
queryClient.setQueryData<PostT[]>(['posts'], (oldData || []) => [ ...oldData, post])
},
})
// deletePost(id: string) {
// await http.delete(`/posts/${id}`);
// }
const mutation = useMutation(deletePost, {
onSuccess: (_, id) => {
queryClient.setQueryData<PostT[]>(['posts'], (oldData || []) => oldData.filter((post) => id !== post.id)
},
})
Invalidating the query can also be an option is some cases. The query will be invalidated and the data will be marked as stale. This will trigger a refetching in the background. So you know for a fact that the data will be as fresh as possible.
This can be handy if you got:
multiple queries to update with data from a mutation
have a (difficult) nested data structure to update
import { useMutation, useQueryClient } from 'react-query'
const queryClient = useQueryClient()
const mutation = useMutation(createPost, {
onSuccess: () => {
queryClient.invalidateQueries('posts')
queryClient.invalidateQueries('meta')
queryClient.invalidateQueries('headers')
},
})
But it really is up to you.
The main advantage of using manual updates comes from the fact that you can do it before the request is sent to the server; so if you manually update after the request is successful, then there's not much of an advantage if the data that you get from the server doesn't need to be immediately present to the user & in those cases (which I have found to be the majority) you better off invalidating. when you use optimistic updates, you assume the request is successful before you send it to server & then if the request fails you just roll back your update. this way your action happens instantly which is a better UX than doing the action, showing a loading spinner or something & then showing the updated state. so I have found it more useful for giving instantaneous feedback to the user than saving an extra request to the server. in most cases (as in yours) you still need to invalidate the query after, because your manually added post doesn't have an id, so you should sync it with the list of posts from the server. so be very careful about that because if you reading from that id somewhere else in that page, it would be undefined & would throw an error. so at the end of the day your mutation is not a great candidate for optimistic update & you should be careful to handle all the problems that can come up with your posts value having a post with no id in it (as opposed to something like a follow action which is just changing a boolean value in your database & you can confidently mutate the cache & undo it if request was not successful). so if we assume that you can handle that problem your useMutation hook would be something like this:
return useMutation(
(payload) => {
queryClient.setQueryData("posts", (oldData) => {
return [...oldData, payload];
});
return createPost(payload);
},
{
onSettled: () => {
queryClient.invalidateQueries("posts");
},
}
);
This is my client-side function that handles clicking a Logout button:
const client = useApolloClient();
const [Logout] = useMutation(LOGOUT);
const handleLogout = async (e) => {
await Logout();
const { data } = await props.fetchUser();
if (data) {
let reset = await client.resetStore();
}
};
This is my logout resolver (the mutation takes in no arguments and returns boolean):
logout:(_, __, { res }) => {
res.clearCookie('refresh-token');
res.clearCookie('access-token');
return true;
}
For some reason, client.resetStore() executes a query I have called getRegionPath, which takes in a String. This function is only called in a few of my React components, locked behind an auth, so once logged out, these components are not accessible and cannot be rendered or routed to.
Here is my getRegionPath resolver.
getRegionPath: async (_, args) => {
const {region_id} = args;
const objectId = new ObjectId(region_id);
const region = await Region.findOne({_id: objectId});
if (region.parentRegion == null) {
return [];
}
else {
const path = await regionPath(region.parentRegion);
return path;
}
}
I have no idea why client.resetStore() might make a call to getRegionPath, none of my components with that query ever run, yet I still get this error:
"Argument passed in must be a single String of 12 bytes or a string of 24 hex characters"
How could resetStore() possibly call another query? Any ideas?
EDIT:
After looking into it, it seems resetStore() refetches active queries. How can I make my getRegionPath query not be active? Here is where it is called conditionally:
{auth ?
<ul>
<WNavItem>
<Breadcrumbs user={props.user} currentRegion={currentRegion} handleSetCurrentRegion={handleSetCurrentRegion}/>
</WNavItem>
</ul> : <></>}
And the component:
const Breadcrumbs = (props) => {
console.log("getting path from breadcrumbs");
const {data} = useQuery(GET_REGION_PATH, {variables: {region_id: props.currentRegion}}, {fetchPolicy: 'network-only'})
if (data) { return (...) }
else return (<></>)
};
I am building feed news using reactjs, but however, I notice that Axios is not returning data back to my browser feed in order to process the data, what am I missing on this code?
const browserFeed = async () => {
const response = await browse();
console.log(response)
setFeed(response.data.results); // undefined
setIntialized(true);
};
export const browse = () => {
axios.get('xxxxxxxxxxx').then((result) => {
return result // undefined
}).catch((error) => {
return error;
})
}
You are missing returning the Promise axios.get returns.
export const browse = () => {
return axios.get('xxxxxxxxxxx').then((result) => {
return result;
}).catch((error) => {
return error;
});
};
You can simply return the Promise though, you don't need to chain another .then just to return the resolved value.
export const browse = () => {
return axios.get('xxxxxxxxxxx').catch((error) => {
return error;
});
};
And to be honest I don't think you want to catch and return the error here either since your component logic is written to assume success. You can return the Promise and handle any rejected promises in a try/catch.
export const browse = () => {
return axios.get('xxxxxxxxxxx');
};
But this is a little boring, may as well just make the axios call in the component. surround all the fetching logic in a try/catch, set the feed in the happy success branch, set any error state in the sad failure branch, and set the initialized state in finally block so no matter pass/fail you can indicate data was fetched.
const browserFeed = async () => {
try {
const response = await axios.get('xxxxxxxxxxx');
console.log(response)
setFeed(response.data.results);
} catch(error) {
// set any error state
} finally {
setInitialized(true);
}
};
This avoids the mixing of the async/await syntax with any promise-chain syntax, which should be avoided.