ReactJS Events: “Pooling”, “Nullification”, & event.persist()

Below is an exploration into the above-mentioned, from the ground up.

Sync vs. Async

First, we need to establish the difference between the synchronous protocol of code execution, and the asynchronous protocol of code execution, in JS.

When some code is synchronous, it is “in sync” with the code being executed before and after it: it will be wholly performed directly after the previous step in the program is finished executing, and before whatever step is next.

When some code is asynchronous, it is “not in sync”, it does not follow this straightforward protocol of execution. Invoking the async code at a specific step does not mean that it will be wholly performed directly after the previous step.

Furthermore, whatever step is next in the code (and subsequent steps thereafter) can be executed before the async code is wholly executed. The async functions will get wholly executed once the execution stack clears, check out this video for a thorough explanation of asynchronous code execution in javascript (I recommend watching the whole thing, but for our purposes, watch 10:24–16:16).


setState( ) May Be Async

If we read the React documentation {scroll to “Using State Correctly”, here }, we can see that — many times — changes in state do not happen as soon as setState()is called. Instead, the inner-workings of React make the decision of when to actually update the state.

It may be milliseconds, so it may be hard to tell, but it is not happening immediately. In fact, React selects the best time to update state and may batch these updates into one larger update. Those times, setState() is async, and React decided when to trigger the process. setState() is not always asynchronous — there are times that the react code will change state synchronously (based on some black-boxed and situation-dependent stuff) — but we cannot depend on it being synchronous.

I think of it sort of like when one uses Git: there is committing the changes and then there is actually pushing the changes. We write the code to “commit” the changes to state, and the inner-workings of React decide when to actually “push” the changes to the state, maybe many / all at once.

So, setState()may be asynchronous: in those many instances the state will not actually be set until after the execution stack is cleared. Plus, React may run extra logic behind the scenes, such as batching state changes into one asynchronous update.


Synthetic Events & Pooling

Synthetic Events are “pooled”, meaning simply that the event object will be taken back / reused by React elsewhere in our application. In order to do this, “pooling” the event involves nullifying its properties after the execution of the callback triggered by the DOM event (the function that gets passed the eObj). This “pooling” is a feature built-in to React for performance reasons, which will be brought up in this section.

In Javascript, non-primitive data types are passed by reference. This means that variables “housing” objects (the non-primitive data types, e.g. arrays, functions, and objects) do not actually “house” the object, but “house” a reference to the memory address of that object.

When it comes to the SyntheticEvent object, “pooling” / “reusing it” is a way of saying that the browser won’t allocate memory for a new event object per event, but instead will use the same event object in-memory for all events.

To go about this, the inner-workings of React “nullify” the values of the keys of this object after the callback / event-handler has finished executing. The keys are reused, but the value to each key is reset to the datatype null. This resetting of all values of the object to null is why the React docs describe the event object as getting “nullified”.

This nullification means that the old values to the keys are no longer in use by our program (the datatype null has taken their place), and so the JS garbage-collector does the job of freeing up the memory that was being used by these values.

So even if you have a variable that points to the SyntheticEvent object in memory (e.g. let eventCopy = eObj), that variable (eventCopy) actually houses a reference to the one object in memory that is being reused by the browser. Therefore eventCopy points to the original eOb. As we have discussed, eObj's properties are soon nullified as preparation for it to be used to house the event data for the next DOM event.

Due to this nullification of the event object’s properties, these properties cannot be accessed in an asynchronous way. By the time your asynchronous functions, say setState(), is actually wholly executed and we are trying to access the event’ properties (e.g. event.currentTarget.value), the event object has been “pooled”, and its properties are null.


The Need For event.persist( ) When Invoking setState( ) — In Certain Circumstances

So, — many times — by the time setState() is fully executed (and the event is actually accessed), the eObj being accessed has been nullified. (setState() “may be asynchronous” as per the documentation, but we cannot depend on it being synchronous). The variable that houses the reference to eObj in memory still houses a correct reference to the one object in memory, but we cannot access values that are no longer associated with it’s keys. To keep the event object from being “pooled” and therefore keep it from being nullified, we call event.persist().

Doing this “removes the synthetic event from the pool”: eObj will not be reused with later DOM events, because React will use other memory to make another event object to take the place of the current eObj. Since the event is not nullified, it keeps its keys’ values, and so we can now interact with the event object asynchronously. At this point, there are two event objects in memory: the one we have persisted and is now never to be pooled again, and the new event object which will be pooled and reused from now on.

(For a demonstration of all of what has been discussed so far, clone down this repo and install & run, open the console to see the code at work. Check the comments in SimpleComponent.js and un-comment console.logs as you use the app to explore React’s management of event objects).

Note: If not using the synthetic event with async functions, then the event is being accessed immediately within the execution of the callback / event-handler, so event.persist() is not necessary.


Alternative To event.persist()

One alternative to using event.persist(): you can store the data you need in a variable (e.g. const example = event.target). The const would house the reference to the DOM object, completely independent of a reference to the event object. In this way, we retain access to the event’s target, while no longer needing to access the target through the event object.

Also:

this.setState({
selectedItem: this.state.stuffArray.find(item => item.id === e.target.dataset.id)
}
);

The above works due to the fact that the value associated with the key of “selectedItem” is evaluated / computed before the object is passed in as the argument to the invocation of setState(). Whatever the expression evaluates to is what is going to be associated as the value to the “selectedItem” key, and this evaluation takes place as the object literal is defined.

The above relies on foundational Javascript showcased below:

// simpler example:let someObj = {a: 1 + 1};console.log(someObj["a"]);
// => 2
// more relevant example:function addTwoToX(x) {
return x + 2;
}
let someOtherObj = {b: addTwoToX(2)}console.log(someOtherObj["b"]);
// => 4

In both cases above, when we are defining the key-value pair in the object, the key is being associated to some expression. The value associated with the key is whatever the evaluation evaluates to, and this is computed as the object literal is defined.


Another Interesting Case You May Run Into

A situation where event.currentTarget is null even with event.persist():
https://github.com/facebook/react/issues/2857


Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade