Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Communicate up component's initial state without useEffect warning

I have a simple date picker component with buttons for preset date ranges.

date picker component with buttons for date range presets

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>
like image 366
montrealist Avatar asked Apr 23 '20 19:04

montrealist


People also ask

Does useLayoutEffect run before useEffect?

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.

Should I always use useEffect?

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.

How do I change my initial state of useState?

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.


Video Answer


1 Answers

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 }));
};
like image 58
Shubham Khatri Avatar answered Oct 19 '22 15:10

Shubham Khatri