What Is Event Pooling in React

What is event pooling in react?

It means that the properties of the event only exist while the callback is active. Adding async to the mix, or storing the event for future use, will fail.

This is easily observed if you try console.log(event) inside an event handler. By the time you inspect the object, most properties on the event object will be null. If you stop execution of the script with debugger; immediately after logging the value, you can inspect the values.

class MyComponent extends React.Component {
handleClick (e){
console.log('The event currentTarget is', e.currentTarget); // DOM element
setTimeout(() => {
console.log('event.currentTarget was', e.currentTarget); // null
}, 1000)
}
render () {
return <button onClick={this.handleClick}>Fire event!</button>
}
}

This will log a DOM element when you click the button, and null a second later. For reasons beyond me, event.target is still stored until the next event occurs, and not nullified.

My react event pooling pseudo example makes sense?

Here is quite simple example of SyntheticEvent/EventPool pattern. Obviously in real life it'll be a bit more complex to better respect of event's behavior, but this snippet have to shed some light on concept.

class SyntheticEvent {
// all the following properties and methods are part of React's
// synthetic event, but we'll skip it here in favor of simplicity

// bubbles: boolean
// currentTarget: DOMEventTarget
// defaultPrevented: boolean
// eventPhase: number
// nativeEvent: DOMEvent
// preventDefault(): void {}
// isDefaultPrevented(): boolean { return true }
// stopPropagation(): void {}
// isPropagationStopped(): boolean { return true }
// target: DOMEventTarget
// timeStamp: number
// type: string

// for simplicity we'll consider here only 3 following properties
isTrusted: boolean
cancelable: boolean
persist: () => void

// this property helps to track status of each synthetic event
status: 'POOLED' | 'PERSISTED' | 'IN_USE'

constructor(status, onPersist: () => void) {
this.status = status;
this.persist = onPersist;
}
}

class EventPool {
private pool: SyntheticEvent[] = [];

constructor(initialPoolSize: number) {
// populating pool with pre-allocated events. We will try to re-use
// them as much as possible to reduce GC load
for(let i = 0; i < initialPoolSize; i++) {
this.allocateNewEvent();
}
}

pullEvent(nativeEvent): SyntheticEvent {
const syntheticEvent = this.getEventFromPool();
this.populateEvent(syntheticEvent, nativeEvent);
return syntheticEvent;
}

tryPushEvent(syntheticEvent: SyntheticEvent): void {
if(syntheticEvent.status !== 'PERSISTED') {
this.clearEvent(syntheticEvent);
}
}

private allocateNewEvent(): SyntheticEvent {
const newEvent = new SyntheticEvent( 'POOLED', () => {
newEvent.status = 'PERSISTED';
});
this.pool.push(newEvent);
return newEvent;
}

private getEventFromPool() {
let event = this.pool.find( e => e.status === 'POOLED' );
if(!event) {
event = this.allocateNewEvent();
}

return event;
}

/** Populates synthetic event with data from native event */
private populateEvent(syntheticEvent: SyntheticEvent, nativeEvent) {
syntheticEvent.status = 'IN_USE';
syntheticEvent.isTrusted = nativeEvent.isTrusted;
syntheticEvent.cancelable = nativeEvent.cancelable;
}

/** Sets all previously populated synthetic event fields to null for safe re-use */
private clearEvent(syntheticEvent: SyntheticEvent) {
syntheticEvent.status = 'POOLED';
syntheticEvent.isTrusted = null;
syntheticEvent.cancelable = null;
}
}

// Usage
const mainEventPool = new EventPool(2);
smth.onClick = nativeEvent => {
const syntheticEvent = mainEventPool.pullEvent(nativeEvent);
userDefinedOnClickHandler(syntheticEvent); // <-- on click handler defined by user
mainEventPool.tryPushEvent(syntheticEvent);
};

Event Pooling - React uses SyntheticEvent which is a wrapper for
native browser events so that they have consistent properties across
different browsers. The event handlers that we have in any react-app
are actually passed instances of SyntheticEvent unless we use
nativeEvent attribute to get the underlying browser event.

Wrapping native event instances can cause performance issues since
every synthetic event wrapper that's created will also need to be
garbage collected at some point, which can be expensive in terms of
CPU time.

React deals with this problem by allocating a synthetic instance pool.
Whenever an event is triggered, it takes an instance from the pool and
populates its properties and reuses it. When the event handler has
finished running, all properties will be nullified and the synthetic
event instance is released back into the pool. Hence, increasing the
performance.

https://stackoverflow.com/a/53500357/1040070

onChange Event Pooling for Slider component in Material UI

I solved this by following the answer from this post.

Here is the final code.

const onChange = (name) => (e, value) => {
setCurrent({
...current,
[name]: value,
});
};

make sure to add the name of the value you are changing when calling onChange

                   <Slider
id='costInflation'
name='costInflation'
value={costInflation}
onChange={onChange('costInflation')}
min={0}
max={50}
valueLabelDisplay='on'
/>

ReactJS can't set state from an event with event.persist()

That's the expected behaviour, because event.persist() doesn't imply that currentTarget is not being nullified, in fact it should be - that's compliant with browser's native implementation.

This means that if you want to access currentTarget in async way, you need to cache it in a variable as you did in your answer.


To cite one of the React core developers - Sophie Alpert.

currentTarget changes as the event bubbles up – if you had a event handler on the element receiving the event and others on its ancestors, they'd see different values for currentTarget. IIRC nulling it out is consistent with what happens on native events; if not, let me know and we'll reconsider our behavior here.

Check out the source of the discussion in the official React repository and the following snippet provided by Sophie that I've touched a bit.

var savedEvent;var savedTarget;
divb.addEventListener('click', function(e) { savedEvent = e; savedTarget = e.currentTarget; setTimeout(function() { console.log('b: currentTarget is now ' + e.currentTarget); }, 0);}, false);
diva.addEventListener('click', function(e) { console.log('same event object? ' + (e === savedEvent)); console.log('same target? ' + (savedTarget === e.currentTarget)); setTimeout(function() { console.log('a: currentTarget is now ' + e.currentTarget); }, 0);}, false);
div {  padding: 50px;  background: rgba(0,0,0,0.5);  color: white;}
<!DOCTYPE html><html><head>  <meta charset="utf-8">  <title>JS Bin</title></head><body>
<div id="diva"><div id="divb"> Click me and see output! </div></div> </body></html>

Weird behavior on event handlers in React

React does something called Event Pooling.

What this essentially means is that, for performance considerations, they re-use events.

At the time when you call setState, internally the object might not be okay to re-use as it might behave in ways you wouldn't expect it to (properties get nulled out once the event has served it's purpose).

It is best to save off the reference in a variable for the value that you need, as you did, and use that instead.

Basically, you are accessing it asynchronously (inside the setState function) and it is advised against doing so.

There is a workaround, but I would also advise against it.

If you want to access the event properties in an asynchronous way, you should call event.persist() on the event, which will remove the synthetic event from the pool and allow references to the event to be retained by user code.

React | Synthetic Event interface

Most of these are inherited from native Event.

You can read about them in the MDN doc.

You can also read about specific SyntheticEvent stuff here.



Related Topics



Leave a reply



Submit