The one behavioural change in a release that deliberately has no features: cleanup functions now run after the screen has been updated rather than before.
useEffect(() => {
const sub = subscribe(id);
return () => {
// 16: runs synchronously during unmount
// 17: runs after the browser has painted
sub.unsubscribe();
};
}, [id]);
The reason is that a synchronous cleanup blocks the visual update, which on a large tree is a visible pause when navigating away. The consequence is that anything reading the DOM in a cleanup may find it already gone, and code that assumed the old timing breaks — which is rare and is the only thing to test for. useLayoutEffect keeps the synchronous behaviour for the cases that need it.