1
I’m having trouble using custom Hooks inside the useEffect, in particular, when defining within the useEffect custom hook methods that alter your internal state. Faced with this problem I came across the following situations.
Model used for the example
import React, { useState } from "react";
const useCounter = () => {
const [count, setCount] = useState<number>(0);
const methods = {
increment: () => setCount((prev) => prev + 1),
};
return { count, ...methods };
};
const Counter: React.FC = (props) => {
const { count, increment } = useCounter();
return (
<div>
<h3>{count}</h3>
<button onClick={() => increment()}>Increment</button>
</div>
);
};
export default Counter;
Supposing it is necessary to execute "increment" in the assembly of the component. This can be done using the useEffect passing an empty dependency array.
useEffect(() => {
increment();
console.log("INCREMENT ON MOUNT...");
}, []);
However, by executing the above code is obtained the warning of the Eslint that "increment" should be added to the dependency array or removed from the useEffect.
Line 17:6: React Hook useEffect has a Missing dependency: 'increment'. Either include it or remove the dependency array React-Hooks/exhaustive-deps
Then the appropriate changes are made:
useEffect(() => {
increment();
console.log("LOOPING...");
}, [increment]);
Now with "increment" added to the dependencies array of useEffect the warning of Eslint disappears, however, the application goes into looping.
Attempts to resolve
At first I thought that this problem could be related there is lack of referential equality between the functions, since they are recreated with each render. So as soon as I got the problem I tried to solve it using the useCallback, since it provides an equal referential function between each component render;
const safeIncrement = useCallback(() => increment(), []);
useEffect(() => {
safeIncrement();
console.log("NO LOOPING...");
}, [safeIncrement]);
The change has returned me a second warning, this time requesting that "increment" was passed as a dependency on useCallback:
Line 14:56: React Hook useCallback has a Missing dependency: 'increment'. Either include it or remove the dependency array React-Hooks/exhaustive-deps
Change made, and flies there, application in looping again:
const safeIncrement = useCallback(() => increment(), [increment]);
useEffect(() => {
safeIncrement();
console.log("LOOPING...");
}, [safeIncrement]);
Passing useCallback to custom hook
const useCounter = () => {
const [count, setCount] = useState<number>(0);
const methods = {
increment: useCallback(() => setCount((prev) => prev + 1), []),
};
return { count, ...methods };
};
Then I passed the useCallback into the custom hook and it seemed to work, no warning, no looping this time... The point is, I don’t know if the practice is right, I’ve never done it, I’ve never seen anyone do it, and my general approach may be wrong. If anyone can give me feedback about this practice I thank you :)
To check the Renders sequence(seems to be occurring correctly):
import React, { useState, useEffect, useCallback } from "react";
const useCounter = () => {
const [count, setCount] = useState<number>(0);
const methods = {
increment: useCallback(() => {
setCount((prev) => prev + 1);
console.log("RENDER - INCREMENT");
}, []),
};
return { count, ...methods };
};
const Counter: React.FC = (props) => {
const { count, increment } = useCounter();
useEffect(() => {
console.log("RENDER - APP");
});
useEffect(() => {
increment();
}, [increment]);
return (
<div>
<h3>{count}</h3>
<button onClick={() => increment()}>Increment</button>
</div>
);
};
export default Counter;