I have a pseudo-code React component similar to this one:
const codeApplier = ({ code, prices }: Props) => {
const [isAccepted, setIsAccepted] = useState(false);
const applyCode = useCallback(async () => {
const validationResult = await callToApplyCode(code, prices);
if (validationResult.status === "OK") {
setIsAccepted(true);
} else {
setIsAccepted(false);
}
//do more stuff
}, [code, prices]);
useEffect(() => {
if (isAccepted) applyCode();
}, [prices]);
return <button onClick={applyCode} />;
};
The important parts:
Now, the component like this works fine, but the linter complains that on the useEffect I am not listing all the dependencies. The infamous exhaustive-deps rule.
The issue is: if I add "applyCode" (or even isAccepted) to the dependencies of the useEffect like this:
useEffect(() => {
if (isAccepted) applyCode();
}, [isAccepted, applyCode, prices]);
then the linter is pleased, but useEffect is going to duplicate the call at each click.
It this one of those cases where disabling exhaustive-deps is ok? Or is there a way to refactor the component without angrying the linter?
I looked at this comment: https://github.com/facebook/react/issues/14920#issuecomment-471070149 and various similar questions on StackOverflow, but I was not able to find an answer to my specific case.
If you want only the change of prices
to trigger the useEffect
, save the previous state of prices
in a ref, and whenever the useEffect
is triggered by deps change, check if current & previous prices are the same. If they are, bail out.
const codeApplier = ({ code, prices }: Props) => {
const prevPricesRef = useRef(prices); // ref for previous state of prices
const [isAccepted, setIsAccepted] = useState(false);
const applyCode = useCallback(async() => {
const validationResult = await callToApplyCode(code, prices);
setIsAccepted(validationResult.status === "OK");
//do more stuff
}, [code, prices]);
useEffect(() => {
if(prices === prevPricesRef.current) return; // bail out if current & prev prices are identical
prevPricesRef.current = prices; // update prev prices
if (isAccepted) applyCode();
}, [prices, isAccepted, applyCode]);
return <button onClick={applyCode} />;
};