I'm sure I'm missing something, but I can't find a clear anwser here. I'm trying to minimize re-renderings, at least avoid useless renderings like in this case.
- The
state
is an object, because I'd like to set bothloading
andproducts
properties with a singlesetState
call - Initially,
loading
istrue
because I'm sure thatuseEffect
is called at least once - Calling
setState
insideuseEffect
will trigger an useless immediate re-render, because I think how object are compared (shallow vs deep)
Actual code:
export const ProductsIndex = () => {
console.log('[ProductsIndex] Rendering');
const [state, setState] = useState({ loading: true, products: [], pages: 1 });
useEffect(() => {
console.log('[ProductsIndex] useEffect');
setState(prev => ({ ...prev, loading: true });
axios.get('/api/products', params)
.then(res => {
setState(prev => ({ ...prev, loading: false, products: res.data });
});
// fetch
}, [params]);
};
I can think at this solution, but I really don't know if there is a better way to handle this use cases:
useEffect(() => {
console.log('[ProductsIndex] useEffect');
setState(prev => prev.loading ? prev : { ...prev, loading: true });
}, []);
CodePudding user response:
It'd be simple enough to only set the state initially if it's different from the current state.
useEffect(() => {
console.log('[ProductsIndex] useEffect');
if (!state.loading) {
setState(prev => ({ ...prev, loading: true });
}
But setting state unnecessarily usually shouldn't be something you need to worry about. It's not a problem for React, and a well designed app usually shouldn't depend on the component not re-rendering at a certain point. If it causes problems for you, you can use various hooks to monitor and account for state/prop changes if needed, especially useMemo
and the cleanup function returned by useEffect
.
If a component happens to re-render twice when an app starts up, or even 5 or 10 times - it'll usually be completely imperceptible to the user, and so isn't worth worrying about. You just need to make sure that other logic in your app is also written with the possibility of re-renderings in mind.
CodePudding user response:
To be honest the way you are going about this seems to be wrong. You can probably rewrite this in a better manner using custom hooks.
import { useEffect, useState } from "react"
import axios from "axios"
export default function useFetch(url){
const [data,setData] = useState(null)
const [error,setError] = useState(null)
const [loading,setLoading] = useState(false)
useEffect(() => {
(
async function(){
try{
setLoading(true)
const response = await axios.get(url)
setData(response.data)
}catch(err){
setError(err)
}finally{
setLoading(false)
}
}
)()
}, [url])
return { data, error, loading }
}
Try something like this. Reference: https://dev.to/shaedrizwan/building-custom-hooks-in-react-to-fetch-data-4ig6
CodePudding user response:
The reason why your first setState
triggers a re-render even though the state objects are identical, is because, as React official documentation explains, states use the Object.is()
comparison algorithm. It is easy to see then what happens:
const obj1 = { loading: true, products: [], pages: 1 };
const obj2 = { ...obj1, loading: true};
Object.is(obj1, obj2); //false
The reason for this behaviour is that, simply, despite having the same values, obj1
and obj2
are different references of objects. So, as @CertainPerformance stated, why not simply control before setting the state?
Another approach might be the one suggested in this very useful question: Does react re-render the component if it receives the same value in state?