Keep a component state as minimal as possible and avoid unnecessary state synchronization .
🚫 Before refactoring - b is a redundant state which should be sync:
export default function App() {
const [a, setA] = useState(1);
const [b, setB] = useState(1);
function updateA(x) {
setA(x);
};
function updateB() {
setB(1 + a);
};
useEffect(() => {
updateB()
},[a])
return (
<div>
<button onClick={() => updateA(a+1)}>Click</button>
<p>a: {a}</p>
<p>b: {b}</p>
</div>
)
};
✅ After refactoring - a is a minimum sufficient state and just derive b from it:
export default function App() {
const [a, setA] = useState(1);
function updateA(x) {
setA(x);
};
function updateB() {
return a+1
};
return (
<div>
<button onClick={() => updateA(a+1)}>Click</button>
<p>a: {a}</p>
<p>b: {updateB()}</p>
</div>
)
};
Top comments (4)
Both examples are anti patterns, I would suggest looking into useMemo for derived/calculated properties
May you clarify what is a problem with a second? If it is just about wrap in useMemo then I say "Measure first".
I would say calling a function inside the template is not great for visibility and debug-ability. Define
const b = useMemo(() => ..., [a])
on top, so you have a clear view of all variables inside your functional component.Defining state variables doesn't cost anything, replacing them with an uncached function call, is not great.
In your example you are doing some very basic calculation on a + 1, but in real life you want to keep your derived variables in useMemo. Keeping strict to good habits will help you preventing performance bugs. It might not look/feel slow, but it adds a little computation to every render.
Regarding first part I agree, it is better for readability to define a separate variable b instead of just a function call.
I would argue that wrap all derived variables in useMemo by default is a good habit. Most of real life function calls which returns derived values are extremely fast. I think you know the phrase about premature optimization and evil=)