3

I want to use react's setState with chrome API but I'm running into an issue...

componentDidMount() {

    chrome.runtime.onMessage.addListener(function(request, sender) {
        if (request.action == "getSource") {
            this.setState({sourceCode: request.source});
        }
    }); 
}

I tried the following but chrome API does not recognize setState as a function, so then I tried to first save request.source as a variable...

componentDidMount() {
    var source = "";
    chrome.runtime.onMessage.addListener(function(request, sender) {
        if (request.action == "getSource") {
            source = request.source;
        }
    });
    this.setState({sourceCode: source});
}

But when I try the following, source remains an empty string. I cannot figure out why since source is being set to request.source. How can I fix this?

EDIT

I am calling a script as so...

chrome.tabs.executeScript(null, {
        file: 'src/js/scripts/getPageSource.js'
     }, function() {
     ...

and inside the script I have the following...

chrome.runtime.sendMessage({
    action: "getSource",
    source: DOMtoString(document)
});

Where DOMtoString function simply returns a string. It is the caught by my componentDidMount which I have verified by printing to the console inside the if statement.

It has come to my attention that the addListener is asynchronous. Is there any way I can save the result in the state?

buydadip
  • 8,890
  • 22
  • 79
  • 154
  • Please [edit] the question to be on-topic: include a **complete** [mcve] that duplicates the problem. Including a *manifest.json*, some of the background *and* content scripts. Questions seeking debugging help ("**why isn't this code working?**") must include: ►the desired behavior, ►a specific problem or error *and* ►the shortest code necessary to reproduce it **in the question itself**. Questions without a clear problem statement are not useful to other readers. See: "**How to create a [mcve]**", [What topics can I ask about here?](http://stackoverflow.com/help/on-topic), and [ask]. – Makyen Nov 09 '16 at 17:54
  • 1
    Are you aware that setState is asunchronous? If you want to see the result you have to ask for them inside a callback function within setState(). ie: this.setState({sourceCode: source}, function(){ console.log(this.state.sourceCode) }) – Falk Nov 09 '16 at 18:03
  • @Makyen inside the `if` statement, if I `console.log` the `source` variable, I get the right output. But outside of the `addListener`, `source` remains an empty string. I'm not sure what else you need. – buydadip Nov 09 '16 at 18:03
  • 1
    We need to see how you send that `source` and what's really inside it obviously :-) Also, yeah, you're incorrectly using asynchronous js as Falk noted. – wOxxOm Nov 09 '16 at 18:04
  • 1
    Possible duplicate: [Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference](http://stackoverflow.com/questions/23667086/why-is-my-variable-unaltered-after-i-modify-it-inside-of-a-function-asynchron) – Makyen Nov 09 '16 at 18:06
  • 2
    I think this is completely on topic, not sure why this is flagged. In any case you're hitting two classic javascript issues. 1) setState is not a function because `this` is getting rebound by your callback function http://stackoverflow.com/questions/31045716/react-this-setstate-is-not-a-function and 2) `source` is an empty string because your setstate call will be fired before your callback function above – azium Nov 09 '16 at 18:08
  • @wOxxOm ok see my edit – buydadip Nov 09 '16 at 18:08
  • Alternate duplicate (for first section of code): [React this.setState is not a function](http://stackoverflow.com/questions/31045716/react-this-setstate-is-not-a-function) – Makyen Nov 09 '16 at 18:21
  • @azium, then you should be voting to close as a duplicate. – Makyen Nov 09 '16 at 18:23

1 Answers1

3

You need to bind this so it is unchanged in the event listener

chrome.runtime.onMessage.addListener(function(request, sender) {
    if (request.action == "getSource") {
        this.setState({sourceCode: request.source});
    }
}.bind(this));

Your second attempt doesn't work because the callback is asynchronous. You need to call setState when the callback returns. In your second attempt, you register for the listener but then immediately call setState.

Edit: Alternatively, you could switch to using arrow functions instead. This would lexically bind this so it would be unchanged.

chrome.runtime.onMessage.addListener((request, sender) => {
    if (request.action == "getSource") {
        this.setState({sourceCode: request.source});
    }
});
AnilRedshift
  • 7,937
  • 7
  • 35
  • 59
  • 3
    You're using react and JSX - easier to use `=>` – Benjamin Gruenbaum Nov 09 '16 at 18:13
  • Agreed. I would add `=>` as an option in your answer. – azium Nov 09 '16 at 18:14
  • 1
    I was preferring the KISS approach but added it as an option. – AnilRedshift Nov 09 '16 at 18:18
  • @BenjaminGruenbaum, No, react and JSX are not the reason. `=>` is available because this is running as a Chrome extension. Thus, you only have to consider Chrome as the browser, which supports `=>` (from v45). In addition, other browsers in which such an extension might also be compatible (e.g. a Firefox WebExtension) also support `=>`. – Makyen Nov 09 '16 at 18:28
  • 2
    @Makyen I think Benjamin was implying that OP is probably transpiling with babel already, so in fact the arrow function will not exist in the compiled code. – azium Nov 09 '16 at 18:35
  • @Makyen I have a chrome extension that needs to run on older versions of Chrome (that don't get updated, on intranet) so my mileage varies - but your argument is valid too and probably more so for more users. – Benjamin Gruenbaum Nov 09 '16 at 18:44
  • @azium, I understood that. I was pointing out that the fact that this is for a Chrome extension supersedes JSX use (for `=>`). On the other hand, Benjamin Gruenbaum makes a good point about compatibility with older versions of Chrome, if a consideration for the OP. The OP has never mentioned using JSX, thus just assuming its use, or any other transpiler, is not a good idea (even if JSX is often used with React). To a large extent, we're arguing semantics for stating assumptions (or realizing we made assumptions). Assumptions we have all made (older Chrome compatibility not needed/JSX use). – Makyen Nov 09 '16 at 19:07