Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do I continue to have stale data in my useEffect?

My useEffect is running as it should be, but once it is complete, it doesn't update the state of my Component. It always shows the previous render. I thought I have added all the appropriate dependencies, but it still gives me stale data. Here is my hook:

export default function useSort(items, sortKey, sortAscending) {
  let [sorted, setSorted] = useState(items);

  useEffect(() => {
    let sortedItems = sortKey ? items.sort((a, b) => {
      if (!a[sortKey] && b[sortKey]) return sortAscending ? -1 : 1;
      if (a[sortKey] && !b[sortKey]) return sortAscending ? 1 : -1;
      if (a[sortKey] > b[sortKey]) return sortAscending ? 1 : -1;
      if (a[sortKey] < b[sortKey]) return sortAscending ? -1 : 1;
      return 0;
    }) : items;
     setSorted(sortedItems);
  }, [items, sortKey, sortAscending]);

  return sorted;
}

Here is the component I am using it in:

const SearchResults = ({ columns, searchResults, sortAscending, sortKey }) => {
  const dispatch = useDispatch();
  let sorted = useSort(searchResults, sortKey, sortAscending);

  return sorted.map((searchResult, index) => {
    return ( ... )
  }

SearchResult renders, but when I try to sort the results (depending on the column header I click), the useEffect code runs. After it is sorted, SearchResult never gets re-rendered to show the change.

How am I using the hook incorrectly? What is the proper usage?

like image 996
jhamm Avatar asked Apr 22 '20 13:04

jhamm


People also ask

How do you prevent stale closures?

The stale closure problem occurs when a closure captures outdated variables. An efficient way to solve stale closures is to correctly set the dependencies of React hooks. Or, in the case of a stale state, use a functional way to update the state.

Why is useEffect rendering twice?

The useEffect callback runs twice for initial render, probably because the component renders twice. After state change the component renders twice but the effect runs once.

Why useEffect keeps getting called?

useEffect(fn, deps); fn is the effectful function, and deps is an array of values it depends on. Every time the component renders, React checks if all the values in the deps array are still the same. If any of them has changed since the last render, fn is run again.

What is a stale closure?

Stale closure is the referencing towards an outdated variable in between renders (from 'React' perspective). That means even when the state/props are updated and component re-renders, some callbacks will still be referencing old variables. Let's see this with an example.


1 Answers

Arrays in JavaScript are reference type; and setState will cause re-render of the component when current value and previous values being different; unlike primitive types, reference types will only marked as changed when their reference change (i.e. shallow comparison). In this scenario when you mutate the array in-place, previous value and next value would be the same from shallow comparison perspective; it's like calling setState(2) when the state is already 2; reconciliation will ignore that. people already mention that in the comments if you return new array (new reference), that would fix it; by the way check out the snippet below for a specific show-case of the problem happening here:

ReactDOM.render(<Test/>, document.getElementById("root"))

function Test(){
    const [array, setArray] = React.useState([2,3,4,5]);
    const renderCounter = React.useRef(0);
    
    function mutateInPlace(e){
        renderCounter.current++;
        // some random in-place mutations
        array[1] = 4;
        array[2] = 11;
        array.sort((a,b)=>a-b);
        setArray(array);
    }
    
    function mutateOutOfPlace(e) {
        renderCounter.current++;
        // creating a new array
        let arrayCopy = array.slice(0);
        setArray(arrayCopy);
    }
    
    console.log("re-rendered");
    return (
        <div>
            {renderCounter.current}
            <br/>
            <button onClick={mutateInPlace}>mutate in-place</button>
            <button onClick={mutateOutOfPlace}>mutate out-of-place</button>
        </div>
    );
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>

<div id="root"></div>
like image 83
Mhmdrz_A Avatar answered Sep 30 '22 14:09

Mhmdrz_A