useSWR integration with pagination on backend - reactjs

When page changes, new query is created and it's data is set to initialData.
In this case user sees initialData before new query data is fetched:
import React from "react";
import fetch from "../lib/fetch";
import useSWR from "swr";
function Component({ initialData }) {
const [page, setPage] = React.useState(1);
const { data } = useSWR(
`/api/data?page=${page}`,
{ initialData }
);
// ...
}
Component.getServerSideProps = async () => {
const data = await fetch('/api/data?page=1');
return { initialData: data };
};
My issue is that initialData is used as a fallback every time I query for new data:
Do you have any ideas on how I can prevent this flicker?

So in react-query, I think there are multiple ways to avoid this:
keepPreviousData: true
this is the main way how to do pagination, also reflected in the docs as well as the examples.
It will make sure that when a query key changes, the data from the previous queryKey is kept on the screen while the new fetch is taking place. The resulting object will have an isPreviousData flag set to true, so that you can e.g. disable the next button or show a little loading spinner next to it while the transition is happening. It is similar in ux what react suspense is going to give us (somewhen).
initialData function
initialData accepts a function as well, and you can return undefined if you don't want initial data to be present. You could tie this to your page being the first page for example:
function Component({ initialData }) {
const [page, setPage] = React.useState(1);
const { data } = useQuery(
['page', id],
() => fetchPage(id),
{ initialData: () => page === 1 ? initialData : undefined }
);
}
keep in mind that you will have a loading spinner then while transitioning, so I think this is worse than approach 1.
since your initialData comes from the server, you can try hydrating the whole queryClient. See the docs on SSR.
initialData works very well with keepPreviousData, so here is a codesandbox fork of the example from the doc where initialData is used (solution 1). I think this is the best take: https://codesandbox.io/s/happy-murdock-tz22o?file=/pages/index.js

Related

How to refresh graphql data on state change

import { useQuery, gql, useMutation } from "#apollo/client";
const Questions = () => {
const [modal, setModal] = useState(false)
const QUESTION_QUERIES = gql`
query getQuestions(
$subjectRef: ID
$gradeRef: ID
$chapterRef: ID
$status: String
) {
getQuestions(
subjectRef: $subjectRef
gradeRef: $gradeRef
chapterRef: $chapterRef
status: $status
) {
id
question_info
question_type
answer
level
published
subjectRef
gradeRef
chapterRef
levelRef
streamRef
curriculumRef
options
status
subject
grade
chapter
stream
curriculum
}
}
`;
const { loading, error, data } = useQuery(QUESTION_QUERIES);
return (
<div>
</div>
)
}
Here is my react graphql code.
I wants to fetch data when modal change using state if modal status change to true to false or false to
true it will make api call to fetch questions again
Please take a look how to solve the issue.
use useLazyQuery:
const [updateFn,{ loading, error, data }]= useLazyQuery(QUESTION_QUERIES);.
Then create useEffect with modal as dependency variable, and call updateFn inside useEffect
You want to fetch data after the modal state change, So you simply use useEffect and put modal in the dependency list of the useEffect and for useQuery there is also a function called refetch, the logic would be like this
const { loading, error, data, refetch } = useQuery(QUESTION_QUERIES);
useEffect(() => {
// the reason I put if condition here is that this useEffect will
// also run after the first rendering screen so you need to put a check
// to do not run refetch in that condition
if (data) refetch();
}, [modal]);

Pull data from firestore using useEffect works on re-render only

Here is my code:
import React, { useEffect, useState } from 'react';
import { getDocs, collection } from 'firebase/firestore';
import { db } from '../firebase-config';
const Home = () => {
const [postList, setPostList] = useState([]);
const postsCollectionRef = collection(db, "data");
useEffect(() => {
const getPosts = async () => {
const data = await getDocs(postsCollectionRef);
let postListArray = []
data.forEach((doc) => {
const post = { ...doc.data() }
postListArray.push(post)
});
setPostList(postListArray)
};
getPosts();
console.log(postList);
}, []);
return (
<div>test</div>
);
};
export default Home;
On loading, the console.log returned an empty array. The spooky thing is when i changed anything , for example
return (
<div>test_epic</div>
);
The console.log shows that it is an array. Anyone has any idea as to why? Please refer to the screepcap as attached.
the first render on loading
I changed anything and components rerendered
Setting state in react is asynchronous, so the data is loaded and the state is set but the console.log statement is executed before the setting state async operation is complete
To make it a bit more clear this is how it works step by step
Component is rendered and postList is initialized with a value of []
useEffect is triggered
Data is fetched
A call to set a new value of postList is placed using setPostList (key here is a call is placed not that the data is actually updated)
You print console.log with a value from Step 1
The call from Step 4 is complete and now the data is actually updated
Here is an article that explains it with examples
And here is another answer that explains this deeply

React component content disappears after page refresh

I am new to react and am having trouble figuring out why the data inside my Content component does not re-render on refresh.
When I visit one of the routes, say /sentences-of-the-day, and then I refresh the page, it seems all the stuff inside content is gone.
Can someone please help me out?
Here is the code sandbox:
https://codesandbox.io/s/mainichome-v7hrq
You need to load the data once the component is mounted (using useEffect) set to local state to trigger the render. In each refresh, mounting happens again and you have the data after each refresh.
Define another function in content.data.js
export const getContentData = () => {
return Promise.all(contentDataStories.map((func) => func()));
};
In your content.component.jsx
import { getContentData } from "./content.data.js";
const [content, setContent] = useState([]);
useEffect(() => {
(async () => {
setContent(await getContentData());
})();
}, []);
Code sandbox => https://codesandbox.io/s/mainichome-forked-4sx5n?file=/src/components/content/content.component.jsx:302-449
The problem is here:
import contentData from "./content.data.js";
//...
const [content] = useState(contentData);
That imports contentData and then sets it as state.
However, that value is asynchronous.
const contentData = [];
contentDataStories.forEach(function (func) {
func().then((json) => {
contentData.push(json);
});
});
export default contentData;
It's just [] until those promises reoslve.
So what's happening is that the page is loading fine, but the content from that file hasn't loaded before the first render.
This is a race condition. Which will happen first, the data loading or the render? Sometimes the render wins and everything is fine, but sometimes it doesn't and you get a blank page.
To fix it, you need to make React aware of your data loading, so that it can re-render when the data finishes loading.
First make a function that does your async loading:
export function getContentData() {
return new Promise((resolve) => {
// fetch async stuff here
resolve(myDataHere)
})
}
And then call that from a useEffect, which sets the state.
function Content() {
const { titleParam } = useParams();
const [content, setContent] = useState(contentData);
useEffect(() => {
getContentData().then(setContent);
}, [getContentData]);
//...
}
Now when you component mounts, it calls getContentData. And then that promise resolves, it sets the state, triggering a a new render.

How to pass data into useState using useEffect - next.js ( swr hook for fetching and cache )

I want to pass data into my useState, the problem is that, useEffect hasn't been able to to perform this operation too well...
This is what i have
const PublicationsHome = ({ data: allPubs }) => {
// All pubs
const { data: Publications }: thePublication = useSWR(
`${process.env.URL}/api/publication`,
{
initialData: allPubs,
revalidateOnFocus: false
}
);
const [pubRequested, setPubRequested] = useState<Ipublication[]>([]);
useEffect(() => {
setPubRequested(Publications);
}, []);
console.log(pubRequested);
`;
return ()
};
export const getStaticProps: GetStaticProps = async () => {
const { data } = await axios.get(`${process.env.URL}/api/publication`);
return {
props: data
};
};
export default PublicationsHome;
So, the logic is quite simple, i'm gettin data by using getStaticProps, that is server side rendering, but i want to use SWR hook, so i'm using initialData so i can use it, and, the las thing is, i'm using useEffect and i want to pass Publications data from swr to my useState, the problem is that, Publications data is slow enough to not be able to pass the data.
As you can see, i have a console.log() and it returns me undefined
What can i do about it, i need that data in my useState, any ideas ?
Thanks people
You need to wait for the data to load.
useEffect(() => {
if(data){
setPubRequested(Publications);
}
}, [data]);

How to derive "loading" from useSWR between fetches without revalidation?

I was asked a question regarding SWRs "loading" state:
How do you create a loading state from SWR between different URL fetches?
Their docs make it appear straight forward:
const { data, error } = useSWR(`/api/user/${id}`, fetcher)
const isLoading = !error && !data;
However, this logic seems to fail after the first render of the hook/component. On the first render data is undefined. Then loads and data becomes a value to consume in the UI.
Let's say I change the id via the UI and want to show loading indicator. Because data is no longer undefined, the same logic fails.
There is an additional item returned isValidating. So I updated my logic:
const isLoading = (!data && !error) || isValidating
However, this could be true when:
there's a request or revalidation loading.
So in theory something else causes my component to rerender. This could inadvertently cause a "revalidation" and trigger loading state gets shown. This could break the UI temporarily, by accident.
So how do you derive "loading" between URL changes without revalidation? I am trying to replicate how graphQL Apollo Client returns const { loading, error, data } = useQuery(GET_DOGS);
Let's say I change the id via the UI and want to show loading indicator. Because data is no longer undefined, the same logic fails.
data will be undefined again when you change the key (id), if it doesn't have a cache value.
Remember that in SWR { data } = useSWR(key) is mentally equivalent to v = getCache(k), where fetcher (validation) just write to the cache and trigger a re-render.
data is default to undefined, and isValidating means if there's an ongoing request.
Alternatively, you can derive loading through the use of middleware. Here's what I use...
loadingMiddleware.ts
import { useState } from 'react'
import { Middleware } from 'swr'
const loadingMiddleware: Middleware = (useSWRNext) => (key, fetcher, config) => {
const [loading, setLoading] = useState(false)
const extendedFetcher = (...args) => {
setLoading(true)
try {
return fetcher(...args)
} finally {
setLoading(false)
}
}
const swr = useSWRNext(key, extendedFetcher, config)
return { ...swr, loading }
}
export default loadingMiddleware
App.tsx
import { SWRConfig } from 'swr'
import loadingMiddleware from './loadingMiddleware'
const App: FC = () => {
...
return (
<SWRConfig value={{ use: [loadingMiddleware] }}>
...
</SWRConfig>
)
}
export default App
Update (12/13/22)
swr#v2 is out and provides isLoading and isValidating properties in the return value of useSWR.
Here's the difference between the two according to the swr docs.
isValidating becomes true whenever there is an ongoing request whether the data is loaded or not.
isLoading becomes true when there is an ongoing request and data is not loaded yet.

Resources