const { useState } = React;
function Person(props) {
console.log('Render Person');
return (<span>{ props.identity.firstName } { props.identity.lastName }</span>);
}
function App (props) {
console.log('Render Hello');
const [count, setCount] = useState(0);
const einstein = { firstName: "Albert", lastName: "Einstein" };
return (
<div>
<button onClick={() => setCount(count + 1)}>Increment</button>
<Person identity={einstein} />
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById('container')
);
The `<Person />` 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 (<button onClick={props.onClick}>Log</button>);
}
function App (props) {
console.log('Render Hello');
const [count, setCount] = useState(0);
const logConsole = () => console.log("HELLO, WORLD");
return (
<div>
<button onClick={() => setCount(count + 1)}>Increment</button>
<Logger onClick={logConsole} />
</div>
);
}
ReactDOM.render(
<App />,
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.
Im doing mostly back-end work these days to keep my sanity.
Folks want to solve their own problems, but they keep getting saddled with React's as well.
> The pain and suffering of hooks all roots from the mismatch between a dogmatic belief in the superiority of immutability and the harsh reality of the host language that is JavaScript (Feb 25, 2023)
This fundamental misalignment with React and JavaScript is the billion dollar mistake.Try SolidJS. Similar ethos to React but far less boilerplate and performance-killing repaints.
Less code is better, functional or not.
I tried Solid. Signals are not new, I've used Knockout before and it turns into a spaghetti mess after a few years. I guess people today simply aren't old enough or experienced enough to know what eventually happens with fine grained reactivity.
The reason I moved away from Vue is precisely due to the spaghetti nature of fine-grained reactivity. It's like people learned nothing from the reactive stream days during Knockout JS. In a big enough app, the state simply becomes unwieldy and I am always grateful for the unidirectional dataflow and explicit setting of data in React.
UI = f(state), and that's how it should be. useState and other hooks seek to keep state around since at the end of the day we need to make components that perform functionality, and it really reminds me of monads in functional programming. Coming from such FP languages, React was and still is a great paradigm.