Advanced React: onToggle event listener

Hi, in the “onToggle event listener” lesson from the Advanced React course, the callback is called from a “useEffect”, which also triggers the first time the component is rendered.

In the next lessons, this is solved by using refs, however, I was wondering: Could we just use the toggle function and call the callback from there?

So instead of:

React.useEffect(() => {
    onToggle()
}, [on])

just

function toggle() {
    setOn(prevOn => !prevOn)
    onToggle()
}

That way there is no need to use refs and it also won’t be triggered the first time the component is rendered.

Hey @Pablo, been a while since I did React so answering from memory. You technically can call onToggle() directly inside toggle() and it would work fine if the only way on ever changes is through that function. That also avoids the “runs on first render” issue you mentioned.

The catch is that setOn is async, so when you call onToggle() right after it, you’re technically still working with the old value of on. Also, if the state ever gets changed from somewhere else (like a reset button, prop change, etc.), your onToggle() won’t run, since it’s only wired to toggle().

I assume that’s why the course leans on useEffect([on]) + a ref. It makes sure the callback always runs in sync with the actual updated state, no matter how on gets changed.

Hi Roku,

Yeah, looking again into my code example it’s true that since the “setOn” is async I would be working with the old value. However, I think it can be solved very easily by calling “onToggle” not after the “setOn” but within it:

function toggle() {
    setOn(prevOn => {
        const newOnValue = !prevOn;
        onToggle(newOnValue);
        return newOnValue;
    });    
}

Anyway, it makes sense to use useEffect + ref for more consistency if other methods could be calling “setOn”.

Thanks

Having onToggle(newOnValue) inside the state updater is a solid workaround. Since the function form of setOn guarantees you’re working with the latest state, you avoid the async pitfall I mentioned earlier.

I think the main trade-off is still scope this approach works as long as all state changes to on go through toggle(). But if on is ever updated from somewhere else (like props syncing it, a reset button, or an effect), then your onToggle won’t fire unless you’re also listening with useEffect([on]).

So I’d say your approach is a clean and pragmatic for controlled scenarios where you know the state is only flipped via toggle(). The useEffect + ref pattern is just a bit more defensive where it guarantees the callback reflects all changes to on, no matter the source.