Pausing react query and re-fetching new data - reactjs

I have a useQuery which is disabled in a react function component. I have another useQuery that uses mutate and on the success it calls refetchMovies(). This all seems to work well but I'm seeing old data in the refetchMovies. Is there a way for to get the refetchMovies to always call fresh data from the server when its called ?
const MyComponent = () => {
const {data, refetch: refetchMovies} = useQuery('movies', fetchMovies, {
query: {
enabled: false
}
})
const {mutate} = useQuery('list', fetchList)
const addList = useCallback(
(data) => {
mutate(
{
data: {
collection: data,
},
},
{
onSuccess: () => refetchMovies(),
onError: (error) => console.log('error')
}
)
},
[mutate, refetchMovies]
)
return (
<div onClick={addList}> {data} </div>
)
}

Try to invalidate the query in your onSuccess callback instead of manually refetching it:
https://tanstack.com/query/v4/docs/react/guides/query-invalidation
Example:
// Invalidate every query with a key that starts with `todos`
queryClient.invalidateQueries({ queryKey: ['todos'] })

Related

Next js: Hydration failed with React Query and React Cookie

So i have narrowed the issue down to the fact that I am using the cookie.logged_in to enable the react query call. If I remove the enabled: !!cookies.logged_in and cookies.logged_in in the if condition, then the code works properly
. I want a scenario where the code goes directly to show the children if the cookies.logged_in is unavailable and only tries to show the loading when both the query. loading is working and cookies.logged_in is actually available
This is my code
const AuthMiddleware: React.FC<AuthMiddlewareProps> = ({ children }) => {
const [cookies] = useCookies(['logged_in']);
const { setCurrentUser } = useContext(Context);
const query = useQuery(['authUser'], () => getCurrentUserFn(), {
enabled: !!cookies.logged_in,
select: (data) => data,
onSuccess: (response) => {
setCurrentUser(response);
},
onError: () => {
setCurrentUser({} as AuthUser);
},
});
if (query.isLoading && cookies.logged_in) {
return <LoadingScreen />;
}
return children;
};

Query never stops fetching after refetching or invalidating

Fetching for the first time works, same goes for resetting the query. The data is fresh right after, then becomes stale after 5 seconds.
However, I want to refetch or invalidate the queries after applying mutations, but whenever I do so, the data just keeps refetching and never returning:
DevTools scr showing data just refetching
Same when i use the DevTools directly.
My useQuery hook:
export const useFetchCandidates = (id: string) => {
return useQuery<Entry<IKandidatFields>[], Error>(
'candidates',
async () => {
const res = await getCandidates(id)
return res
},
{
staleTime: 5000,
cacheTime: 10,
}
)
}
Using the hook to access data:
const {
data: candidates,
}: UseQueryResult<Entry<IKandidatFields>[], Error> = useFetchCandidates(id!)
The mutation:
const mutation = useMutation(
() =>
sendCandidateToNextStage(candidateID, utlysning).then(() =>
getCandidates(id!)
),
{
onMutate: () => {
queryClient.cancelQueries(['candidates', id])
},
onSettled: () => {
queryClient.resetQueries(['candidates', id])
},
}
)
This was solved by using a get axios request instead of utilizing the cms client directly... still don't know why the one works instead of the other when both of them return the same object.
When using the contentful API client:
export async function getCandidates(
id: string
): Promise<Entry<IKandidatFields>[]> {
const res = await client
.getEntries<IKandidatFields>({
content_type: 'kandidat',
})
return res
}
it was constantly fetching and never worked.
However, when using an axios request instead
export const getCandidatesFromAPI = async (id: string) => {
const { data } = await axios.get(
`https://cdn.contentful.com/spaces/${spaceID}/environments/${environmentId}/entries?access_token=${accessToken}&content_type=${contentTypeKandidat}`
)
return data
}
as the mutation function, everything worked perfectly.

SWR hook not reflecting database change

This component is for counting views at page level in Next.js app deployed on AWS Lambda
function ViewsCounter({ slug }: { slug: string }) {
const { data } = useSWR(`/api/views/${slug}`, fetcher);
const views = new Number(data?.total);
useEffect(() => {
const registerView = () =>
fetch(`/api/views/${slug}`, { method: "POST" })
.catch(console.log);
registerView();
}, [slug]);
return (
<>
{views}
</>
);
}
This one is for displaying views on homepage
function ViewsDisplay({ slug }: { slug: string }) {
const { data } = useSWR(`/api/views/${slug}`, fetcher);
const views = new Number(data?.total);
return (
<>
{views}
</>
);
}
While it works as expected on localhost, looks like it displays only the first fetched value and doesn't revalidate it for some reason.
When visiting the page, Counter is triggered correctly and the value is changed in DB.
Probably it has something to do with mutating, any hints are appreciated.
useSWR won't automatically refetch data by default.
You can either enable automatic refetch using the refreshInterval option.
const { data } = useSWR(`/api/views/${slug}`, fetcher, { refreshInterval: 1000 });
Or explicitly update the data yourself using a mutation after the POST request to the API.
function ViewsCounter({ slug }: { slug: string }) {
const { data, mutate } = useSWR(`/api/views/${slug}`, fetcher);
const views = new Number(data?.total);
useEffect(() => {
const registerView = () =>
fetch(`/api/views/${slug}`, { method: "POST" })
.then(() => {
mutate();
})
.catch(console.log);
registerView();
}, [slug]);
return (<>{views}</>);
}

How to use data stored in cache with SWR hooks, and how to make SWR fetch only one time

I have a component that fetch data on mount thanks to a useEffect hooks. I'd like it to not refetch data on mount, and instead use the 'cached' data provided by useSwr hooks when i re-navigate to this component. Im not sure how to do this. What i have read is that you can call swr like this :
const { data } = useSwr('same route as previous one')
and it 'll gives you the data stored in cache by the previous swr call.
const CategoryList = ({setLoading}) => {
const [category, setCategory] = useState('');
const [mounted, setMounted] = useState(false);
const [parameters, setParameters] = useState({});
const company_id = localStorage.getItem('company_id')
const session = new SessionService();
const { dataFromFetch, error } = useSWR([mounted ? `${session.domain}/company/${company_id}/category-stats` : null, parameters ], url =>
session.fetch(url, {
method: 'GET',
})
, {
onSuccess: (dataFromFetch) => {
setCategory(dataFromFetch)
setLoading(false)
setMounted(false)
},
onError: (err, key, config) => {
console.log("error", err)
}
}
)
useEffect(() => {
setMounted(true)
setLoading(true)
}, [])
return (
<div className={classes.CategoryList}>
<h5>Parc de véhicules</h5>
<div className={classes.CategoriesCards}>
{category.data? category.data.map((element, index) => {
return <CategoryItem
category={element.data.name}
carNumber={element.stats.nb_vehicles}
locating={element.stats.nb_booked}
available={element.stats.nb_available}
blocked={element.stats.nb_unavailable}
percentage={(element.stats.nb_booked / element.stats.nb_vehicles * 100).toFixed(2)}
key={index}
/>
}): null}
</div>
</div>
)
}
export default CategoryList;
Plus, in an other hand, i'd like my SWR hooks not to consistently try to refetch data like it is dooing actually. What i tried is passing options after my fetcher function, like it is stipulate in this post SWR options explanation. Actually my component is trying to refetch data every 5-10seconds, though unsuccesfully thanks to my 'mounted' condition which result in a 'null' route ( which is the recommended way to do it according to the documentation ). It still sends a request with response 404, which i'd like to avoid.
const [parameters, setParameters] = useState({
revalidateOnFocus: false,
revalidateOnMount:false,
revalidateOnReconnect: false,
refreshWhenOffline: false,
refreshWhenHidden: false,
refreshInterval: 0
});
const company_id = localStorage.getItem('company_id')
const session = new SessionService();
const { dataFromFetch, error } = useSWR([mounted ? `${session.domain}/company/${company_id}/category-stats` : null, parameters ], url =>
session.fetch(url, {
method: 'GET',
})
, {
onSuccess: (dataFromFetch) => {
setCategory(dataFromFetch)
setLoading(false)
setMounted(false)
},
onError: (err, key, config) => {
console.log("error", err)
}
}
)
According to SWR's documentation, the hook's API is
const { data, error, isValidating, mutate } = useSWR(key, fetcher, options)
In your code, you're passing the options as part of an array in the first argument, when it should be the third.
A minor refactor shows how it can be fixed:
const parameters = {
revalidateOnFocus: false,
revalidateOnMount: false,
revalidateOnReconnect: false,
refreshWhenOffline: false,
refreshWhenHidden: false,
refreshInterval: 0,
};
const company_id = localStorage.getItem( "company_id" );
const session = new SessionService();
const fetcher = (url) =>
session.fetch(url, {
method: "GET",
});
const key = mounted
? `${session.domain}/company/${company_id}/category-stats`
: null;
const { data, error } = useSWR(key, fetcher, parameters );
You've also over-complicated your code quite a bit - there's no need for onSuccess or onError handlers - you can simply use the returned values data and error instead.
Also, no need to save the fetched data to state by using setCategory. Just read directly from data. That's the benefit of of SWR 🙂 It will auto-magically trigger re-renders when data is fetched.

React-Query and Query Invalidation Question

I don't really know how to ask clearly but, I will paste my code first and ask below.
function useToDos() {
const queryCache = useQueryCache();
const fetchTodos = useQuery(
'fetchTodos',
() => client.get(paths.todos).then(({ data }: any) => data),
{ enabled: false }
);
const createTodo = async ({ name ) =>
await client.post(paths.todos, { name }).then(({ data }) => data);
return {
fetchTodos,
createTodo: useMutation(createTodo, {
onMutate: newItem => {
queryCache.cancelQueries('fetchTodos');
const previousTodos = queryCache.getQueryData('fetchTodos');
queryCache.setQueryData('fetchTodos', old => [
...old,
newItem,
]);
return () => queryCache.setQueryData('fetchTodos', previousTodos);
},
}),
};
}
As you can see, I am trying to create my own custom hooks that wrap react-query functionality. Because of this, I need to set my fetchTodos query to be disabled so it doesn't run right away. However, does this break all background data fetching?
Specifically, when I run createTodo and the onMutate method triggers, I would ideally like to have the fetchTodos query update in the background so that my list of todos on the frontend is updated without having to make the request again. But it seems that with the query initially set to be disabled, the background updating doesn't take effect.
As I don't think wrapping react-query hooks into a library of custom hooks is a very great idea, I will probably have more questions about this same setup but for now, I will start here. Thank you. 😊
The mutation does not automatically triggers a refetch. The way to achieve this using react-query is via queryCache.invalidateQueries to invalidate the cache after the mutation. From the docs:
The invalidateQueries method can be used to invalidate and refetch single or multiple queries in the cache based on their query keys or any other functionally accessible property/state of the query. By default, all matching queries are immediately marked as stale and active queries are refetched in the background.
So you can configure the useMutation to invalidate the query when the mutation settles. Example:
function useToDos() {
const queryCache = useQueryCache();
const fetchTodos = useQuery(
'fetchTodos',
() => client.get(paths.todos).then(({ data }: any) => data),
{ enabled: false }
);
const createTodo = async ({ name ) =>
await client.post(paths.todos, { name }).then(({ data }) => data);
return {
fetchTodos,
createTodo: useMutation(createTodo, {
onMutate: newItem => {
queryCache.cancelQueries('fetchTodos');
const previousTodos = queryCache.getQueryData('fetchTodos');
queryCache.setQueryData('fetchTodos', old => [
...old,
newItem,
]);
return () => queryCache.setQueryData('fetchTodos', previousTodos);
},
onSettled: () => {
cache.invalidateQueries('fetchTodos');
}
}),
};
}
What about splitting the logic into two different hooks? Instead of a monolith like useToDos?
That way you could have a hook for fetching:
const fetchData = _ => client.get(paths.todos).then(({ data }: any) => data)
export default function useFetchTodo(
config = {
refetchOnWindowFocus: false,
enabled: false
}
) {
return useQuery('fetchData', fetchData, config)
}
And in your mutation hook you can refetch manually, before createTodo
import useFetchTodo from './useFetchTodo'
//
const { refetch } = useFetchTodo()
// before createTodo
refetch()

Resources