Reputation: 25062
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
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