jhamm
jhamm

Reputation: 25062

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?

Upvotes: 6

Views: 178

Answers (1)

Mechanic
Mechanic

Reputation: 5380

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>

Upvotes: 4

Related Questions