skip to Main Content

I’m currently learning how to use function components in react and got a bit of a timing problem. As it seems, the event that should trigger a rerender is called between the rendering of the component and the call to the useEffect hook. Is there any delay between those two that would allow an async event to be executed? And how would I prevent this "gap"? (I do not remember about every having such a problem with class components and the componentDidMount function for registering my eventListener back then).

Given this component (with very simplified types and logic):

// global variable. May have already been updated from before CounterDisplay is even rendered the first time
let globallyStoredCounter = 0; 

export default function CounterDisplay(): React.JSX.Element {
    useEvent('counter_changed_event');

    console.log('Start rendering CounterDisplay: ', globallyStoredCounter);
    return (
        <div>{globallyStoredCounter}</div>
    );
}

with this custom event hook:

export function useEvent(eventName: string) {
    const [lastEventInstance, setEventInstance] = useState<object>({});

    useEffect(() => {
        function handleEvent(event: object) {
            console.log('Trigger a rerender by updating the state with a new object');
            setEventInstance(event);
        }

        console.log('Listening to event ' + eventName);
        window.addEventListener(eventName, handleEvent);
        return () => {
            console.log('Stop listening to event');
            window.removeEventListener(eventName, handleEvent);
        };
    }, [lastEventInstance]);
    return [lastEventInstance];
}

and finally, the code to dispatch the event. This is executed by the same button press, that also triggers the CounterDisplay Component to render.

fetch('http://localhost/api/something').then(_ => {
   globallyStoredCounter += 1;
   console.log(`Notifying event "counter_changed_event" with data`, {counter: globallyStoredCounter});
   window.dispatchEvent(new Event('counter_changed_event'));
}); 

I get this output in the console:

> Start rendering CounterDisplay: 0
> Notifying event "counter_changed_event" with data {counter: 1}
> Listening to event counter_changed_event

While the HTML output keeps showing the value 0.
And to be clear: Yes, the {counter: 1} object is an entirely new object that is definitely, even shallowly, different from the previous.

I hope someone can help me with this, as this is at the moment a nasty race condition.


Edit: As it seemed like I did not explain my problem very well (sorry for that!), I updated the code above to include how the event will be called and here is some more Info, that will (hopefully) make it more clear:

The Situation:
I got the component and hook as described above and want to always display the current value of my globallyStoredCounter (it should not matter, when it was or will be updated). The counter itself is updated asynchronously and triggers the event listener afterwards.

My expectation vs reality:
If globallyStoredCounter is updated externally BEFORE rendering the component, it shows the updated number.
If globallyStoredCounter is updated AFTER applying the useEvent hook, it shows the updated number.
But sometimes, the event is called between rendering and the event listener in the useEffect hook being placed, thus not updating the component.

The question:
Im surely not the first or only one with independently updated data and a component to reflect this data. But I do not know how to keep the component updated, if a change is happening between rendering and the useEffect hook. Can anyone tell me, how to avoid this gap and keep showing the correct number?

2

Answers


  1. If you store a variable outside of the component (and React) you’ll have to do a lot of work to make sure it’s correct and component lifecycle behaviours might change between React versions.

    I’d strongly suggest trying to find a solution within React to store your variable, be it a prop, state, context, etc.

    If you clarify why you need that globallyStoredCounter outside of the component and updated elsewhere, me or someone else can help with concrete code solution.

    Login or Signup to reply.
  2. If you need to run code synchronously before the component renders, you can use the useLayoutEffect hook instead of useEffect

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search