React: document.addEventListener callback cannot update the component

Created on 22 Apr 2017  路  9Comments  路  Source: facebook/react

This may be a bug.

In my simple react-app, inside a component's componentDidMount() I have a
document.addEventListener('message', function(e) {//callback})

However, the callback of this event listener DOES NOT update the component even after calling
this.setState{states:states} inside it

I can confirm indeed that the callback is fired, but it does not update the component.

Is this a bug? What would be a work around for this?

Most helpful comment

@OlegLustenko and @pavelsuraba are correct. The context here:

document.addEventListener('message', function(e) {
   // this is `document`. Calling `this.setState` will raise an exception
})

Will be the document. The options they suggest are:

Use an arrow function. This is preserves the scope to that of the parent closure - the component:

document.addEventListener('message', (e) => {
   this.setState({ stuff: true })
})

Use Function.prototype.bind. This returns a copy of a function with a specific context:

document.addEventListener('message', function (e) {
   this.setState({ stuff: true })
}.bind(this)) // this is the component

Hope it helps!

All 9 comments

Hi, I don't see any problem, could you give example.

Or maybe you should check context of function call ?
https://jsfiddle.net/taqhr2bL/1/

Hi, this shouldn't work since in addEventListener the context for this keyword is bound to the document. You should receive an error. The way around this is 1. use arrow function for your callback, which bind this automatically. 2. use bind() to bind the value of this keyword.

@OlegLustenko and @pavelsuraba are correct. The context here:

document.addEventListener('message', function(e) {
   // this is `document`. Calling `this.setState` will raise an exception
})

Will be the document. The options they suggest are:

Use an arrow function. This is preserves the scope to that of the parent closure - the component:

document.addEventListener('message', (e) => {
   this.setState({ stuff: true })
})

Use Function.prototype.bind. This returns a copy of a function with a specific context:

document.addEventListener('message', function (e) {
   this.setState({ stuff: true })
}.bind(this)) // this is the component

Hope it helps!

If you have es6 at your disposal, use bind

document.addEventListener('mousedown', this.onMouseDown.bind(this));

When onMouseDown runs it will no longer be bound to #document but to the this that set the event listener

@nhunzaker How do I do that with Hooks?

@Tarrasque18 It'll depend on what you need to do, but I think you could do that like:

import React, { useEffect } from 'react'

function MessageComponent() {
  useEffect(() => {
    const onMessage = event => {
      // handle event
    }

    document.addEventListener('message', onMessage) 

    return () => {
      document.removeEventListener('message', onMessage) 
    }
  }, [])

  // return JSX
}

@Tarrasque18 It'll depend on what you need to do, but I think you could do that like:

import React, { useEffect } from 'react'

function MessageComponent() {
  useEffect(() => {
    const onMessage = event => {
      // handle event
    }

    document.addEventListener('message', onMessage) 

    return () => {
      document.removeEventListener('message', onMessage) 
    }
  }, [])

  // return JSX
}

If you do this, you need to pass also your hook like :

import React, { useEffect, useState } from 'react'

function MessageComponent() {
  const [loading, setLoading] = useState(false)
  useEffect(() => {
    const onMessage = event => {
      // handle event
    }

    document.addEventListener('message', onMessage) 

    return () => {
      document.removeEventListener('message', onMessage) 
    }
  }, [loading, setLoading])

  // return JSX
}

this will work but , if you are rendering something by setting some same here , than it will go slow after two or three state changes

any idea why ! how to reduce the render

Was this page helpful?
0 / 5 - 0 ratings