How to use useEffect hook properly with array dependency. I passed state from redux store and still my component renders infinitely
Solution 1
You can make a custom hook to do what you want:
In this example, we replace the last element in the array, and see the output in the console.
import React, { useState, useEffect, useRef } from "react";
import ReactDOM from "react-dom";
import { isEqual } from "lodash";
const usePrevious = value => {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
};
const App = () => {
const [arr, setArr] = useState([2, 4, 5]);
const prevArr = usePrevious(arr);
useEffect(() => {
if (!isEqual(arr, prevArr)) {
console.log(`array changed from ${prevArr} to ${arr}`);
}
}, [prevArr]);
const change = () => {
const temp = [...arr];
temp.pop();
temp.push(6);
setArr(temp);
};
return (
<button onClick={change}>change last array element</button>
)
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Live example here.
Solution 2
Your effect is triggered based on the "shopUsers" prop, which itself triggers a redux action that updates the "shopUsers" prop and thats why it keeps infinitely firing.
I think what you want to optimize is the rendering of your component itself, since you're already using redux, I'm assuming your props/state are immutable, so you can use React.memo
to re-render your component only when one of its props change.
Also you should define your state/props variable outside of your hooks since they're used in the scope of the entire function like so.
In your case, if you pass an empty array as a second param to memo, then it will only fire on ComponentDidMount, if you pass null/undefined or dont pass anything, it will be fired on ComponentDidMount + ComponentDidUpdate, if you want to optimise it that even when props change/component updates the hook doesn't fire unless a specific variable changes then you can add some variable as your second argument
React.memo(function(props){
const [isLoading, setLoading] = useState(false);
const { getStoreUsers, shopUsers } = props;
useEffect(() => {
setLoading(true);
getStoreUsers().then(() => {
setLoading(false);
}).catch((err) => {
setLoading(false);
});
}, []);
...
})
shubham choudhary
Updated on June 09, 2022Comments
-
shubham choudhary about 2 years
I am using useEffect hook and getting a list of users data with fetch call using function getStoreUsers which dispatches an action on response and stores shopUsers(which is an array) inside the redux store.
In array dependency, I am writing [shopUsers]. I don't know why it is causing infinite rendering.
Here is how I am using useEffect hook:
useEffect(() => { const { getStoreUsers, shopUsers } = props; setLoading(true); getStoreUsers().then(() => { setLoading(false); }).catch(() => { setLoading(false); }); }, [shopUsers]);
I want to re-render component only when data inside shopUsers array changes.
If I write shopUsers.length inside array dependency. It stops to re-render.
But, let's suppose I have have a page which opens up when the user clicks on a userList and updates user data on next page. After the update I want the user to go back to the same component which is not unmounted previously. So, In this case array length remains the same, but data inside in of array index is updated. So shopUsers.length won't work in that case.
-
shubham choudhary about 5 yearsyeah, redux action updates the shopUsers when component mount mounts for first time. so let's consider shopUsers initially set to null and when component renders. useEffect gets executed. It hits the API and sets the shopUsers to data with users which is array of objects. So now shopUsers changed it re-renders, and tried to hit api again, and gets shopUsers again, but data is now same as previous. so, from next time onwards it should not trigger re-render. I have one doubt. does useEffect performs a shallow comparison on array and objects?
-
Khaled Osman about 5 yearsI don't get what you're trying to do, it feels weird to me.. why would you want to update a variable that it, itself is used to trigger a call that updates itself? I don't understand the usecase, but most commonly what you want to do is have a parent component that makes the API call on componentDidMount, or a useEffect(fn, []) with empty array as second argument to be triggered once, and pass the result as props to the child component to render them. And then whenever you want to make the API call again it should be in an event handler in the parent component that will update the props.
-
shubham choudhary about 5 yearsThanks,looks like your solution can work for me. Can you explain few things first here, the parameters we pass the array as dependency, does react performs shallow comparition just like isEqual does? if not, then don't you think it should secondly, is this useRef thing introduced with redux hooks only or was it previously also there?
-
Khaled Osman about 5 yearsyou can listen to some event or pass a callback prop thats triggered when the user navigates back to trigger the API call to fetch data again, that's much cleaner than making an API call every time the component updates or a prop gets changed.
-
Colin Ricardo about 5 years
isEqual
uses deep comparison.useRef
is a hook provided by React, yes. It's used similarly toReact.createRef()
. -
shubham choudhary about 5 yearsYeah, sorry I was talking about deep comparison :)
-
Colin Ricardo about 5 yearsAs of now React only does shallow comparisons, but that might change in the future!
-
shubham choudhary about 5 yearsYou are right callback can be a solution.But, I mostly avoid using callbacks. It makes login flow unpredictable. In one case I have a third screen in app which once updates navigates back to same page. So I will need to pass callback from one screen to another and then to third. and passing data as params using react navigation takes lot work. I am too lazy for that
-
shubham choudhary about 5 yearsYou were right. I was being weird. Now I got this now.
-
Khaled Osman about 5 yearsif you don't want to pass the same props to multiple components I think you can use
Context
for that to render something different based on some shared variable