React’s useEffect and arrays in its’ dependency array
Could I get any more arrays into that title? I’m working on a React app at the moment. Adopting hooks has made my life a bit easier. But useEffect
‘s dependency array took a while to get my head around – specifically what to do if your only dependency is itself an array.
If useEffect
‘s dependency array itself contains an array, like this, you get an infinite loop and effectively perform a DOS attack on your own API.
const Thing = () => { const [arrayOfThings, setArrayOfThings] = useState([]); useEffect(() => { // Imagine some code here to // fetch things from an API setArrayOfThings(loadedArrayOfThings); }, [arrayOfThings]) return ( <ul> {arrayOfThings.map(el => (<li>{el.title}</li>))} </ul> ) }
I imagine it’s because useEffect
isn’t doing a deep compare on the dependencies. So, perhaps changing it so useEffect
is dependent on the length of the array will help?
useEffect(() => { // Imagine some code here to // fetch things from an API setArrayOfThings(loadedArrayOfThings); }, [arrayOfThings.length])
Well, it does, but it still requests the data from the API twice – once on first render, and again after the first fetch because the length of the array changes. The fix I found is to use a second useState variable:
const Thing = () => { const [arrayOfThings, setArrayOfThings] = useState([]); const [doFetchData, setDoFetchData] = useState(true); useEffect(() => { if (doFetchData) { // Imagine some code here to // fetch things from an API setArrayOfThings(loadedArrayOfThings); setDoFetchData(false) } }, [doFetchData]) return ( <ul> {arrayOfThings.map(el => (<li>{el.title}</li>))} </ul> ) }
The ensures the data is only loaded once. Additionally, it gives us a way to precisely control when to go and re-fetch the data (using setDoFetchData(true)
). Better.