Other Tips I Don't Know Where To Put
⚠️ Each of these tips might be better suited to somewhere else but I can't figure out where. Chuck your suggestions in #chapter-native-apps or open an MR. ⚠️
Use IIFEs to conditionally determine primitives
In components, it's often necessary to conditionally determine a value based on other values. A common way people do this is with a function:
const Component = ({ maxStake }) => {
const [stake, setStake] = useState(0);
const getButtonLabel = () => {
if (stake >= maxStake) {
return "Max state reached";
} else if (stake === 0) {
return "Please enter a stake";
} else {
return String(stake);
}
};
return <Button>{getButtonLabel()}</Button>;
};
This isn't necessarily bad, and it works well in a lot of cases. But it can become an issue when that function is needed inside hooks or is passed to memoised props. This can often result in a "cascade" of memoisation, where functions have to be wrapped in useCallback
to satisfy dependency arrays.
const Component = ({ hideFooter, maxStake }) => {
const [stake, setStake] = useState(0);
// This will now need to be memoised to maintain its reference
// between renders and not break the useMemo.
const getButtonLabel = () => {
if (stake >= maxStake) {
return "Max state reached";
} else if (stake === 0) {
return "Please enter a stake";
} else {
return String(stake);
}
};
const FooterComponent = useMemo(() => {
if (hideFooter) return null;
return <Button>{getButtonLabel()}</Button>;
}, [getButtonLabel, hideFooter]);
return <View>{FooterComponent}</View>;
};
This function is only returning a primitive value, so memoising the function to determine it (assuming it's inexpensive to calculate) is overkill when the value can be determined during render using an immediately invoked function expression
or IIFE.
const Component = ({ hideFooter, maxStake }) => {
const [stake, setStake] = useState(0);
const buttonLabel = (() => {
if (stake >= maxStake) {
return "Max state reached";
} else if (stake === 0) {
return "Please enter a stake";
} else {
return String(stake);
}
})();
const FooterComponent = useMemo(() => {
if (hideFooter) return null;
return <Button>{getButtonLabel()}</Button>;
}, [buttonLabel, hideFooter]);
return <View>{FooterComponent}</View>;
};
In doing this we are able to determine the value during the render, and because it is a primitive there is no need to memoise and any equality comparison will be comparing a primitive value. This is basically achieving the same result as useMemo
in that it is returning a value, but without memoisation.
Really, this is a more elegant alternative to determining this at the top level of the component via a single or multiple expressions:
const Component = () => {
// logic
let label: String;
const stakeExceedsMax = stake >= maxStake;
const noStake = stake === 0;
label = stakeExceedsMax
? "Max state reached"
: noStake
? "Please enter a stake"
: String(stake);
// more logic
};
This isn't a replacement for useMemo
however, and that still should be used for expensive computations, and to memoise non-primitive types used as dependencies or props on optimised components.