React app keeps sending continuous HTTP requests - reactjs

I have a page that displays data fetched from a MongoDb through API, in this page, you can modify the data and after that, the page will render again to display the new data. But inspecting the network requests I noticed my react app sends an infinite number of requests, which obviously slows down everything. I read this is caused by this snippet of code:
useEffect(() => {
fetchData();
}, [users]);
I also read I must empty the dependencies array of the useEffect, but If I do so, the page will not re-render if the data changes (for example after inserting a new record in the db).
This is the function I use to get the data from the db:
const [users, setUsers] = useState([]);
async function fetchData() {
const res = await fetch("http://localhost:8000/users/");
if (res.status === 401) {
console.log(res.json);
} else {
setUsers(await res.json());
}
}
How can I fix this? Thanks.

You created an infinite loop:
fetchData calls setUsers, which sets users. The effect reacts to changes to users, and calls fetchData again. ♾️
I don't know your exact use case, but one solution would be to only call fetchData when an actual user interaction has happend in your app that makes you want to fetch new data.

Related

Fetching and rendering records individually or in batches for an unknown total of records

I'm currently working on a ruby on rails + react app, fetching and rendering some records with axios and useEffect. It works fine as is, displays the data after fetching all.
How could I fetch/render individually or in controlled batches? Meaning I would like to fetch for example 1 by one or 5 at a time and render after each record/batch is available (so the list would be progressively growing from the user point of view). I'm not looking for either pagination nor infinite scroll.
The total of records varies and is unknown.
const [isLoading, setIsLoading] = useState(false)
useEffect(() => {
setIsLoading(true)
recordGateway.getRecords(recordId)
.then(res => setOtherRecords(res.data.other_records))
.catch(() => setHasError(true))
.finally(() => (setIsLoading(false)))
}, [])
getRecords = (recordId: number) =>
this.axios.get<{ other_records: OtherRecord[] }>(
`/api/v1/record_card/${recordId}/other_records`
)
I'm reading documentation about offset and promises but can't seem to understand it enough to apply it or maybe that's not at all what I should be looking for.
I would suggest, you use Promise.allSettled () alongside a state variable to hold the records in the array. You can batch your Axios API calls inside it. The promise will be resolved after all your API calls are complete.
When the user scrolls to the end of the list, you can start a batch
request & expand the state array with the batch result.
I'm outlining an example for you below. Adjust it according to your needs.
State
const [records, setRecords] = useState([]);
Batch API calls
useEffect(() => {
// your other codes
// ...
// ...
Promise.allSettled([
recordGateway.getRecords(recordId),
recordGateway.getRecords(recordId),
recordGateway.getRecords(recordId),
// add more if you need
]).then(results => {
const fetchedRecords = results.map(r => r.value.data.other_records);
setRecords([...records, ...fetchedRecords])
})
// your other codes
//...
//...
}, [])

Why is the initial state getting printed along with the data?

I am fetching data from a backend api.
const Home = () => {
const [posts,setPosts]=useState([]);
useEffect(()=>{
const fetchPosts= async ()=>{
const res = await axios.get("/posts")
setPosts(res.data);
}
fetchPosts();
},[])
console.log(posts)
However in doing this, the initial state of posts which is [] is also getting printed.
Why is this happening and how can I avoid this and only get the data?
that is the reason why you see loading in real-world App because communication between server and client takes time so we show loading or show error if API gets error the same logic goes to component or element whatever your are trying to show as well
if(posts.length > 0) {
console.log(posts)
} else {
console.log("Loading...")
}

Firebase firestore read operation very high

So basically im making a CRUD app using react and firebase firestore for the backend.
My write and delete operation is doing well, there is no problem with it.
But my read operation have problem.
My web is getting all document from a collection in firebase using useEffect. So this only run whenever it first mount (when my web load first time) and when im changing "users" value when doing delete and create operation
this my code:
useEffect(() => {
const getUsers = async () => {
const querySnapshot = await getDocs(collection(db, "cobadata"));
setUsers(querySnapshot.docs.map((doc)=> ({...doc.data(), id: doc.id})))
};
getUsers();
}, [users]);
idk whats wrong but im getting a very high read operation when im test the web, its like every one read operation i do in my website, its getting like hundred operation in the firebase. i can see this in my firebase console, when im using the web just like 5 minute in my firebase console the read operation reaching 20k< operation.
can anyone help me how to deal with this, thanks!
You dont show all of your code here, so I will need to do some guessing.
Your useEffect has a dependency array that now is set to [users]. This means that every time the variable users changes your useEffect will rerender. Inside your useEffect you then set a new value to users by the setUsers function. Even if you get the same values returned from firebase regarding the current users, you still create a new array each time you read data. (querySnapshot.docs.map((doc)=> ({...doc.data(), id: doc.id}))). React only does a shallow comparison, meaning that the object reference has changed, and therefore users is different on each render.
First you need to decide when you want to run the useEffect and what should trigger it. If changes in the variable users is not the correct place to check, then I would remove users from the dependency array.
One solution could be to move the functionality in your effect into its own function and wrap it in an useCallbac. You can then call this function from an ´useEffect` on initial load, and after that simply load the effect whenever you delete or create users. Something like this.
const getUsers = useCallback(async () => {
const querySnapshot = await getDocs(collection(db, "cobadata"));
setUsers(querySnapshot.docs.map((doc)=> ({...doc.data(), id: doc.id})))
}, [collection])
useEffect(() => {
getUsers()
}, [getUsers]);
const createUser = () => {
...
getUsers()
}
const deleteUser = () => {
...
getUsers()
}
(PS! I would recommend adding the eslint-plugin-react-hooks to your eslint-config. This will give you some warning if your hooks are used wrong)

How to show loading state only if data is not received yet. Next.js ssr

I have multiple getServerSideProps in my project and I have a header which displays pages and I have to wait for a page to be opened once I click upon it since I need data to be fetched. Once they are fetched the page will be open.
One approach I used to show user a loading state is to use routeChangeStart BUT I stumbled upon one problem and so I would like not to use this case.
If I go on a page and the data is fetching I want to show user a spinner or some indicator and once the data is fetched I want to stop the indicator/spinner.
As you probably figured out, getServerSideProps runs on the server and is blocking. The fetch request needs to complete before the HTML is sent to the user (i.e., the page is changed). So if you want to show a loading indicator, you need to move that fetch request to the client.
For instance, if you probably have a page with this basic structure:
export default function Page({ data }) {
return <div>{data.name}</div>
}
export async function getServerSideProps() {
const response = await fetch('https://example.com/api')
const data = await response.json()
return {
props: { data },
}
}
const fetcher = url => fetch(url).then(res => res.json());
export default function Page() {
const { data } = useSWR('https://example.com/api', fetcher)
if (!data) return <LoadingSpinner />
return <div>{data.name}</div>
}
Or if you don't need SWR and can use a simple fetch request:
export default function Page() {
const [data, setData] = useState()
useEffect(() => {
fetch('https://example.com/api')
.then(async(response) => {
const json = await response.json()
setData(json)
})
})
if (!data) return <LoadingSpinner />
return <div>{data.name}</div>
}
P.S. If the initial fetch request in getServerSideProps used sensitive information (e.g., API secret credentials), then go ahead and setup a Next.js API route to handle the sensitive part and then fetch the new route.
I just used routeChangeStart.
I didn't want to use it since router.push('/map') didn't work in pages/index.tsx file but I solved this issue by creating a new component putting router.push in useeffect and rendering a loader.
routeChangeStart was in _app.js and because of this in index.js router.push() didn't work - I tested it
routeChangeStart - how it works?
When we click on a page the data is being fetched on the server and the page will only be displayed to us once the data is fetched. So we can make the next thing, we can just intercept the route change.
When we click on a link(we wait for data to fetch) we set loading state in routeChangeStart to true and if we moved to another page(it means we fetched the data) we invoke routeChangeComplete which runs once we moved to the route we wanted to, and here we set loading state to false. And after this I just pass the loading state using React Context

React save api requests

I am building a small online shopping web app and I have about 10 categories of products e.g. Mens, Womens, Kids etc. I want to memoize the requests. My question is how do I do it? I read about useCallback hook and I believe that I should use it here, but at the same time since there are like 10 categories how do I keep track for which category should I get from the cache and for which should I make the request? I have just considering declaring useState hooks for each category and change state for each depending on selected category.
const [productsMensCategory, setProductsMensCategory] = useState([]);
const [productsWomensCategory, setProductsWomensCategory] = useState([]);
const [productsKidsCategory, setProductsKidsCategory] = useState([]);
I have also been thinking that Redux might be useful here.
Could anyone please guide me a bit here, I would like to know how to approach this.
This is how I make a request.
const [products, setProducts] = useState([]);
useEffect(() => {
const getProducts = async (category: string) => {
try {
const response = await getCategoryProducts(category);
console.log(response);
if (response.status === 200) {
setProducts(response.data);
}
} catch (e) {
console.log(e);
}
};
getProducts(handleChangeCategory(section));
}, [section, setProducts]);
If you want these to stay saved even if you reload page you could use localStorage to make it easy. Redux would be a nice approach to not make another request, but as far as you reload it won't save state.
UseCallback only memorizes a callback, so if re-render happens this callback remains the same, it only changes if a dependency from its dependencies array changes
Good idea to have the data memoized. In my opinion, if at all you can get the data with flags based on date_modified for each category (as server response) you can then set or not set component state.
Something like below
if (response.status === 200 && category_flag_from_server !== current_category_flag_from_state) {
setProducts(response.data);
}

Resources