I have a simple date picker component with buttons for preset date ranges.
The problem is in my useEffect
: I'm using it to communicate initial state up on render, but of course React issues a warning ("useEffect has missing dependencies").
Is there a good pattern to do this?
Child:
const LAST_7 = "LAST_7";
let to, from, filter;
// figure out values for "from", "to", and "filter" (which is set to LAST_7 in case "from" and "to" are not in props)
const initial = {
from,
to,
filter,
};
const [state, setState] = useState(initial);
useEffect(() => {
props.onUpdate(from, to);
}, []);
const handleClick = (e) => {
const filter = e.target.value;
const { from, to } = getDatesFromFilterValue(filter);
setState((prev) => ({ ...prev, from, to, filter }));
props.onUpdate(from, to);
};
Parent:
const onDatesUpdate = (from, to) => {
setState((prev) => ({ ...prev, from, to }));
};
// ...
<Child
onUpdate={onDatesUpdate}
></Child>
useLayoutEffect and useEffect are only being executed after a component did mount lifeycle. If we take a look at the result above, we can see that anything in the return statement gets executed first, aka the render cycle. The next hook that gets executed is the useLayoutEffect hook, right after is the useEffect hook.
Always use useEffect for asynchronous tasks Instead of writing asynchronous code without useEffect that might block the UI, utilizing useEffect is a known pattern in the React community — especially the way the React team has designed it to execute side effects.
To set a conditional initial value for useState in React:Pass a function to the useState hook. Use a condition to determine the correct initial value for the state variable. The function will only be invoked on the initial render.
eslint warnings are there to warn users about inapproriate use of useEffect. Since hooks depend a lot on closures its extremely important that we write them properly
Now the reason that eslint warns you for missing dependency is because you use onUpdate
inside of useEffect
Now ESlint is good not not extremely intelligent to figure out what you as a developer want
Its absolutely allright to want the function to be called only on initial render. However ESlint doesn't know if you have intervals or subscriptions within the function which depend on the closure variables and thus warns you that onUpdate might need to be re-run whenever its recreated or its dependency changed
If you are absolutely sure that what you are writing is correct, you could disable the warning like
useEffect(() => {
props.onUpdate(from, to);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
Now that your intention is to call the onUpdate on from and to change which you do via handleClick, it actually right to add them as dependency too
const [state, setState] = useState(initial);
useEffect(() => {
props.onUpdate(from, to);
}, [from, to]);
const handleClick = (e) => {
const filter = e.target.value;
const { from, to } = getDatesFromFilterValue(filter);
setState((prev) => ({ ...prev, from, to, filter }));
};
Now one last thing, you can add onUpdate as a dependency to useEffect if you write it with a useCallback in its parent to make sure that its only created when needs
const Parent = () => {
const [state, setState]= useState({});
const onUpdate = useCallback((from, to) => {
setState(prev => ({
// use functional setState here to get updated state using prev
// and return the updated value
}))
}, [])
...
return (
<Child onUpdate={onUpdate} />
)
}
Child
const [state, setState] = useState(initial);
useEffect(() => {
props.onUpdate(from, to);
}, [from, to, onUpdate]);
// Now that onUpdate is created on once, adding it to dependency will not be an issue
const handleClick = (e) => {
const filter = e.target.value;
const { from, to } = getDatesFromFilterValue(filter);
setState((prev) => ({ ...prev, from, to, filter }));
};
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With