If you are interested to know more indepth on why the array items is changed (sorted) but React doesn't render, there are 2 things to take note:
- How
array.sort work
- How React re-render with
useState
For (1), it's easy, array.sort return the sorted array. Note that the array is sorted in place, and no copy is made. Hence sortItems and items still refer to the same array
For (2), it's a little bit complicated as we have to read through React code base.
This is React.useState signature
export function useState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
const dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
Use Github navigation tools and scan we gonna end-up to this code:
useState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
currentHookNameInDev = 'useState';
mountHookTypesDev();
const prevDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnMountInDEV;
try {
return mountState(initialState);
} finally {
ReactCurrentDispatcher.current = prevDispatcher;
}
}
Next we must find the definition of mounState:
function mountState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
...
const dispatch: Dispatch<
BasicStateAction<S>,
> = (queue.dispatch = (dispatchAction.bind(
null,
currentlyRenderingFiber,
queue,
): any));
return [hook.memoizedState, dispatch];
}
Notice, the return type of mountState is an array where the 2nd argument is an function just like const [items, setItems] = useState([])
Which means we almost there.
dispatch is the value from dispatchAction.bind
Scan through the code we gonna end up at this line:
if (is(eagerState, currentState)) {
// Fast path. We can bail out without scheduling React to re-render.
// It's still possible that we'll need to rebase this update later,
// if the component re-renders for a different reason and by that
// time the reducer has changed.
return;
}
Last part is what function is does:
function is(x: any, y: any) {
return (
(x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y) // eslint-disable-line no-self-compare
);
}
It simply check for equality using === operator.
Comeback to your sort function, nextState in our case is sortItems and prevState is items. With (1) in mind, sortItems === items => true so React gonna skip the rendering.
That's why you see most of the tutorials states that you have to do shallow copy.
By doing so your nextState will differ from your prevState.
TLDR:
- React use function
is above to check for state changes when using hooks
- Always make shallow copy when working with array, object if you are using hooks