Take this example:
const { useState } = React;
function Person(props) {
console.log('Render Person');
return ({ props.identity.firstName } { props.identity.lastName });
}
function App (props) {
console.log('Render Hello');
const [count, setCount] = useState(0);
const einstein = { firstName: "Albert", lastName: "Einstein" };
return (
setCount(count + 1)}>Increment
);
}
ReactDOM.render(
,
document.getElementById('container')
);
The `` component will redraw on each `increment`. Nothing changed. Why would it redraw? Because when the `App` component redraws, the `einstein` variable points to a new reference. React sees this as a change and redraws both `App` and `Person`. What looks like normal JavaScript here is not. To prevent this redraw, you have to opt out like this:
const einstein = useMemo(() => ({ firstName: "Albert", lastName: "Einstein" }), []);
Or know to move the reference outside of the `App` like this:
const einstein = { firstName: "Albert", lastName: "Einstein" };
function App (props) {
...
}
Which is fine in this case because there are no dependencies on the component tree. This is the most common mistake I see in React that leads to bugs. It's not just objects, but also functions.
This is also a redraw:
const { useState } = React;
function Logger(props) {
console.log('Render Log')
return (Log);
}
function App (props) {
console.log('Render Hello');
const [count, setCount] = useState(0);
const logConsole = () => console.log("HELLO, WORLD");
return (
setCount(count + 1)}>Increment
);
}
ReactDOM.render(
,
document.getElementById('container')
);
Why? Because on increment, the `logConsole` is a reference to a new function. So the `Logger` redraws as well. So here, you need to opt out once again by using `useCallback` or moving the function out of the component tree (fine in this case since there are no dependencies). The thing is that it looks like normal JavaScript but the React render cycle is the unseen; you have to be aware of moving things "out of the way" and "bringing them back" via a hook.
React's render cycle re-evaluates entire component sub-trees for changes and if your component doesn't explicitly opt-out by preserving referential equality (`useState`, `useCallback`, `useMemo`, etc.), you'll trigger a redraw downstream. These hooks effectively move the references out of the component tree and pull them back in when the tree re-renders and thus preserve referential equality.
So what teams might do is after experiencing this one time chasing down a bug is wrap every single declaration in a hook to reduce the mental burden. This then creates other issues like performance and memory.
Vue, for example, is the opposite because it has fine-grained reactivity. Nothing redraws until you opt in by using the Vue reactivity primitives.