Search code examples
javascriptreactjsecmascript-6thislodash

debounce and react window resize this reference issue


I am using react and lodash's debounce method. The issue I am having is updating state when the user resizes the window.

The issue I am having is that this is referring to the window as opposed to the component when the user resizes the window in this function:

window.addEventListener('resize', this.delayedCallback)

I have tried setting const that = this etc. but cannot get the correct scope. Does anyone know how to resolve this issue?

See code below:

class Card extends Component {

  constructor(props) {
    super(props)
    this.state = {
      cardElWidth: null
    }
    this.delayedCallback = debounce(this.setCardWidth, 1000);
    this.CardEl = React.createRef()
  }

  componentDidMount () {
    this.setCardWidth()
    window.addEventListener('resize', this.delayedCallback)
  }

  setPlayerCardWidth() {
    this.setState({
      cardElWidth: this.CardEl.current.offsetWidth
    })
  } ... rest of code

Solution

  • Bind the setCardWidth method to this in the constructor:

    constructor(props) {
      super(props)
      this.state = {
        cardElWidth: null
      }
      this.setCardWidth = this.setCardWidth.bind(this)
      this.delayedCallback = debounce(this.setCardWidth, 1000)
      this.CardEl = React.createRef()
    }
    

    Or even shorter by binding directly in the debounce expression:

    constructor(props) {
      super(props)
      this.state = {
        cardElWidth: null
      }
      this.delayedCallback = debounce(this.setCardWidth.bind(this), 1000)
      this.CardEl = React.createRef()
    }
    

    Instead of using bind in the constructor, you can convert the setCardWidth to a class property, and use an arrow function to automatically bind to this.

    Note: this requires babel's plugin-proposal-class-properties.

    setPlayerCardWidth = () => {
      this.setState({
        cardElWidth: this.CardEl.current.offsetWidth
      })
    }
    

    If you use class properties, you can remove the constructor as well:

    class Card extends Component {
      state = {
        cardElWidth: null
      }
    
      CardEl = React.createRef()
    
      componentDidMount() {
        this.setCardWidth()
        window.addEventListener('resize', this.delayedCallback)
      }
    
      componentWillUnmount() {
        window.removeEventListener('resize', this.delayedCallback)
      }
    
      delayedCallback = debounce(this.setCardWidth, 1000)
    
      setPlayerCardWidth = () => {
        this.setState({
          cardElWidth: this.CardEl.current.offsetWidth
        })
      }
    }