0

I'm hoping someone can shed some light on how React's state and event handlers interact with an event handler set directly on the window object itself(outside of the react lifecycle). For example, in the following code sample we set an event handler directly on the window object. Now my understanding of how the browser is handling window events is that when the event fires the browser is placing the callback function into the event loop's queue.

In this case, where the handler is set directly on the window, the enqueuing of the event handler function into the event loop is happening outside/independent of the React component lifecycle. However, the event handler calls React's setState which asynchronously updates the component state. The updating happens asynchronously so that React can more efficiently batch the state changes together so as to avoid re-rendering too often.

import { Component, createRef } from "react";

class GridView extends Component {

  constructor(props) {
    super(props);
    this.state = {
      hasScrolled: false
    }
    
    this.handleScrollEvent = this.handleScrollEvent.bind(this);
  }

  componentDidMount() {
    window.addEventListener("scroll", this.handleScrollEvent);
  }

  componentWillUnmount() {
    window.removeEventListener("scroll", this.handleScrollEvent);
  }

  handleScrollEvent() {
    this.setState({hasScrolled: true})
  }

  render() {
    return <div > ... some scrollable content </div>
  }
  
}

My question is this: Can I be certain that when the handleScrollEvent callback is fired for the first time, after the page initially scrolls, that the component's hasScrolled state value will update to true before the next firing of handleScrollEvent?

My understanding is that the asynchronous nature of React's setState makes it so that I cannot be 100% certain of when the state will be updated in relation to an event being triggered that is bound to the window. Am I understanding this correctly?

To go even further, let's say I was to move the scroll event handler into the React component lifecycle by adding an onScroll callback inside the component's render method like below. My assumption is that even though the scroll event handler and the state update are both managed within React that I still can't be sure of the exact ordering of callback and state changes because event handlers in React are asynchronous and batched just like setState.

import { Component, createRef } from "react";

class GridView extends Component {

  constructor(props) {
    super(props);
    this.state = {
      hasScrolled: false
    }
    
    this.handleScrollEvent = this.handleScrollEvent.bind(this);
  }

  handleScrollEvent() {
    this.setState({hasScrolled: true})
  }

  render() {
    return <div onScroll={ this.handleScrollEvent } > ... some scrollable content </div>
  }
  
}

So, can you ever really be sure of the ordering of state updates in relation to event handlers in React? If the answer is no, you cannot ever be 100% sure, then should logic inside of event handlers rely on a component's instance variables rather than a component's state given that instance variables are updated synchronously?

Drew Reese
  • 165,259
  • 14
  • 153
  • 181
louism2
  • 350
  • 4
  • 18
  • Does this answer your question: https://reactjs.org/docs/faq-state.html#what-is-the-difference-between-passing-an-object-or-a-function-in-setstate ? – Janez Kuhar Feb 24 '21 at 21:40
  • 1
    Javascript is single-threaded. AFAIK, the Javascript event loop is processed in the order they are enqueued. Again, AFAIK, in React, all enqueued state updates are also processed in the order they were enqueued. Neither will ever change order. The order is one of the guarantees I think you could count on. The event handler is 100% synchronous, meaning, from when the event occurs and the callback is enqueued, is completely synchronous. I don't see any way for the callbacks to get out of order. – Drew Reese Feb 24 '21 at 21:40
  • 1
    If you are asking about ***when*** React state updates, here is a simple rule to live by, if the next state depends on any previous state *in any way* at all (even order), then use a functional state update, otherwise a conventional update (like `this.setState({ hasScrolled: true })` is fine. – Drew Reese Feb 24 '21 at 21:47

0 Answers0