How to solve this missing dependency in useEffect? - reactjs

The goal is to reset a value, that is mutated inside of the function, if the props have changed to something that is different from the current value.
I am trying out react hooks and am still not confident how to solve this and if it can be solved.
While this seems to work as expected, I get a ESlint warning:
Warning:(62, 8) ESLint: React Hook useEffect has a missing dependency: 'value'. Either include it or
remove the dependency array. (react-hooks/exhaustive-deps)
function (info) {
const initialValue = info.value;
const [value, set_value] = useState(initialValue);
useEffect(() => {
if (initialValue !== value) {
set_value(initialValue);
}
}, [initialValue]);
...};
I can't add the value variable to the dependency array, because it will prevent the mutation.
I checked the react-hooks documentation and this thread here:
https://github.com/facebook/react/issues/15865
But am still not confident how to apply this knowledge to my case.

You don't need value in useEffect scope, you can use functional updates:
function App({ value: initialValue }) {
const [value, set_value] = useState(initialValue);
useEffect(() => {
set_value((prevValue) =>
initialValue !== prevValue ? initialValue : prevValue
);
}, [initialValue]);
return <></>;
}

Related

Wrapping useState hook with custom setter and clean-up functionality

I'd like to create a custom React hook that
wraps a state (created with useState),
runs a clean-up function up when the component unmounts,
executes a custom setter logic that derives a value,
and exports the custom setter and the internal state value.
export function useCustomState() {
const [value, setValue] = useState();
useEffect(() => {
return () => {
// do cleanup
};
}, [value]);
function customSetValue(newValue) {
if (value) {
// do cleanup for the previous value
}
const derivedValue = derive(newValue);
setValue(derivedValue);
}
return [value, customSetValue];
}
I can achieve this with the code above. My problem arises when I use the returned values in an useEffect hook as a dependency, since the returned custom setter is always a new function reference.
const Component = () => {
const [value, setValue] = useCustomState();
useEffect(
() => {
setValue(simpleValue);
},
[setValue],
);
return <p>{ value }</p>;
};
When I don't include the setter as a dependency of the useEffect, the re-rendering stops, since the dependency does not change after a render. I can omit that reference and disable eslint for the line. This is one solution.
I'd like to know whether it is possible to create a custom referentially stable setter function?
I also have tried using useMemo that stores the custom setter function, but the dependencies for the memo still include the internal value and setValue references, since I want to do clean-up and set the new derived value. If the derived value is not the same for the same input, the dependency cycle will result in infinite re-rendering.
You can pass a function to setValue to remove the dependence on value; instead, the current value is passed as an argument. As a simpler alternative to useMemo, useCallback will give you a consistent function:
export function useCustomState() {
const [value, setValue] = useState();
useEffect(() => {
return () => {
// do cleanup
};
}, [value]);
const customSetValue = useCallback((newValue) => {
setValue((oldValue) => {
if (oldValue) {
// do cleanup for the previous value
}
const derivedValue = derive(newValue);
return derivedValue;
});
}, []);
return [value, customSetValue];
}

React hook missing dependency of custom hook setter

I'm well aware of what the Hook has missing dependency is, what it means and why it's important to watch all dependencies, but this one is just weird.
export function Compo() {
const [value, setValue] = useState<number>();
useEffect(() => {
setValue(Date.now());
}, []);
return (
<>{value}</>
);
}
works fine, but:
function useValue() {
return useState<number>();
}
export function Compo() {
const [value, setValue] = useValue();
useEffect(() => {
setValue(Date.now());
}, []);
return (
<>{value}</>
);
}
show the well known React Hook useEffect has a missing dependency: 'setValue'. Either include it or remove the dependency array react-hooks/exhaustive-deps.
What you've noticed in your example is a quirk of the rule react-hooks/exhaustive-deps. It gives special privilege to hooks it is aware of, and knows to be "stable" under certain circumstances.
Quoting the implementation:
// Next we'll define a few helpers that helps us
// tell if some values don't have to be declared as deps.
// Some are known to be stable based on Hook calls.
// const [state, setState] = useState() / React.useState()
// ^^^ true for this reference
// const [state, dispatch] = useReducer() / React.useReducer()
// ^^^ true for this reference
// const ref = useRef()
// ^^^ true for this reference
// False for everything else.
source: https://github.com/facebook/react/blob/v17.0.1/packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js#L152
Specifically, this part of the rule seems to be what is exempting the useState hook's setter under these circumstances:
if (name === 'useState') {
const references = resolved.references;
for (let i = 0; i < references.length; i++) {
setStateCallSites.set(
references[i].identifier,
id.elements[0],
);
}
}
// Setter is stable.
return true;
The unfortunate result of the hook being helpfuln/clever is that it can lead to confusion where its inference doesn't work, like the scenario you just described.

React hooks a functional update causes the eslint no-shadow error

I'm using React useEffect to update a state. Eslint warns I'm making no-shadow error. I think this is related to some eslint or eslint plugin settings because CRA doesn't cause the same error. How to fix it?
function Sample(props) {
const { flag } = props;
const [value, setValue] = useState();
useEffect(() => {
if (flag) {
setValue(value => value + 1);
}
}, [flag]);
}
Here, setValue(value => value + 1); the value causes no-shadow due to declared at the useState.
eslint is correctly complaining because you've declared a value variable in your useState hook, but then you're re-declaring another variable called value in your useEffect hook.
The simplest fix is to not shadow the variable name by changing the variable in your setValue call -
useEffect(() => {
if (flag) {
setValue(prev => prev + 1);
}
}, [flag]);

Custom react hook can not be called in useEffect with empty dependencies

Im new to react hook, Im doing a project with new feature "Hooks" of react.
I've faced a problem and I need an explain for it.
As document, to implement "componentDidMount", just pass empty array in dependencies argument.
useEffect(() => {
// some code here
}, []);
And I can call dispatch function to updateState inside this useEffect.
const [flag, setFlag] = useState(false);
useEffect(() => {
setFlag(true);
}, []);
Above code works perfectly without warning or any errors.
Now I have my custom hook, but I can not call my dispatch inside the effect.
const [customFlag, setCustomFlag] = useCustomHook();
useEffect(() => {
setCustomFlag(true);
}, []);
This is my custom hook.
function useCustomHook() {
const [success, setSuccess] = useState(false):
const component = <div>{ success ? "Success" : "Fail" }</div>;
const dispatch = useCallback(success => {
setSuccess(success);
}, []);
return [component, dispatch];
}
With above code, it requires me to put setCustomFlag inside the dependencies array.
I do not understand why. What is different between them?
Thanks for sharing.
Probably, your custom hook returns different instance of setCustomFlag on each call. It means, that useEffect() will always use first value (returned on first render). Try to memoize it by calling useCallback()/useMemo() hooks:
function useCustomHook() {
...
const setCustomFlag = useCallback(/* setCustomFlag body here */, []);
}
It would be nice to have your custom hook source to say more.
The reason is setFlag from useState is a known dependency, its value won't change between render, hence you don't have to declare it as a dependency
React eslint-plugin-react-hooks can't be sure about your custom hook, that's why you need to put that into the dependency list
This is taken from eslint-plugin-react-hooks
// Next we'll define a few helpers that helps us
// tell if some values don't have to be declared as deps.
// Some are known to be static based on Hook calls.
// const [state, setState] = useState() / React.useState()
// ^^^ true for this reference
// const [state, dispatch] = useReducer() / React.useReducer()
// ^^^ true for this reference
// const ref = useRef()
// ^^^ true for this reference
// False for everything else.
function isStaticKnownHookValue(resolved) {
}

Call a Redux Action inside a useEffect

My objective here is to call an action inside a useEffect.
const ShowTodos = (props) =>{
useEffect(()=>{
props.fetchTodos()
},[])
}
const mapStateToProps = (state)=>{
return {
todos:Object.values(state.todos),
currentUserId:state.authenticate.userId
}
}
export default connect(mapStateToProps,{fetchTodos})(ShowTodos)
It works fine but I got a warning
React Hook useEffect has a missing dependency: 'props'. Either include it or remove the dependency array react-hooks/exhaustive-deps.
But if I'm going to add props as my second parameter in my useEffects then it will run endlessly.
My first workaround here is to use the useRef but it seems that it will always re-render thus re-setup again the useRef which I think is not good in terms of optimization.
const ref = useRef();
ref.current = props;
console.log(ref)
useEffect(()=>{
ref.current.fetchTodos()
},[])
Is there any other workaround here ?
That is an eslint warning that you get if any of the dependency within useEffect is not part of the dependency array.
In your case you are using props.fetchTodos inside useEffect and the eslint warning prompts you to provide props as a dependency so that if props changes, the useEffect function takes the updated props from its closure.
However since fetchTodos is not gonna change in your app lifecycle and you want to run the effect only once you can disable the rule for your case.
const ShowTodos = (props) =>{
const { fetchTodos } = props
useEffect(()=>{
fetchTodos()
// eslint-disable-next-line import/no-extraneous-dependencies
},[])
}
const mapStateToProps = (state)=>{
return {
todos:Object.values(state.todos),
currentUserId:state.authenticate.userId
}
}
export default connect(mapStateToProps,{fetchTodos})(ShowTodos)
You however can solve the problem without disabling the rule like
const ShowTodos = (props) =>{
const { fetchTodos } = props
useEffect(()=>{
fetchTodos()
},[fetchTodos])
}
I however will recommend that you know when exactly should you disable the rule or pass the values to the dependency array.
You have to add fetchTodos to dependencies.
const ShowTodos = ({ fetchTodos }) => {
useEffect(() => {
fetchTodos();
}, [fetchTodos])
...
}
or like this.
const ShowTodos = (props) => {
const { fetchTodos } = props;
useEffect(() => {
fetchTodos();
}, [fetchTodos])
...
}

Resources