How to set state in react hooks only if state is different from current value

Viewed 1294

I have the following code

setInterval(() => {
             fetch(`/clusters?type=${type}`)
                .then((response) => response.json()
                  .then((responseBody) => {
                    if(clusters !== responseBody.members)
                        setClusters(responseBody.members);
                  }))
                .catch();
    }, 5000);

Since clusters is set asynchronously when check if(clusters !== responseBody.members) clusters is always empty so which is right way to do it? I'm trying to do it in order to have less renders since that request happens every 5 seconds

2 Answers

That is because the state you're getting is stale already. What you can do is create a ref using useRef and then inside another useEffect just update the ref with the current value of your state. And then after fetching the data use ref to compare it to the state, like this:

function App() {
  const [clusters, setClusters] = useState([]);
  const clustersRef = useRef(clusters);

  useEffect(() => {
    clustersRef.current = clusters;
  });

  useEffect(() => {
    setInterval(() => {
      fetch(`/clusters?type=${type}`)
      .then((response) => response.json()
      .then((responseBody) => {
          if(clustersRef.current !== responseBody.members)
            setClusters(responseBody.members);
      }))
      .catch();
    }, 5000);
  }, []);
}

Hope this helps :)

You can pass a function to setClusters that gets the current state as an argument. Then you can compare and either return the new items if there is something new, or the old value if nothing has changed. React is smart enough to know that it should not re-render if you set state to the exact same value.

But you have another problem. React is already smart enough not to re-render if your state hasn't changed. But your state is changing. Your api call is creating new data objects that will not be directly comparable to your old ones.

So, you need to figure out a better way to detect if something has changed. The === or !== wont tell you have two arrays have the same items, they will tell you if they are the same exact instance of array.

[1,2,3] === [1,2,3] //-> false

So you have to come up with a way to tell if you have two arrays contain the same content. One way to do that is to create a string of ids from the old and new data and then compare those. (Assuming clusters is an array of objects that have some uniq id on them). You create a single value that can be compared to another single value to see if anything has changed.

setClusters(prevClusters => {
  const prevIds = prevClusters.map(member => member.id).sort().join('-')
  const newIds = responseBody.members.map(member => member.id).sort().join('-')

  if (newIds === prevIds) {
    return prevClusters // nothing has changed, return previous value
  } else {
    return responseBody.members // return new values.
  }
})
Related