2

Note: This question is not asking how to bind the event handlers. It's asking why, without binding, this is not consistent in the Button's onClick (it refers to an object) and the Form's onSubmit (it's undefined).

Full question:

I was trying to check what happens to this object inside a method if I don't bind the method inside the constructor. What I found is that the result is different for Form and Button. Below is my code for better understanding:

const {Button, Form, Input} = Reactstrap;

class Test extends React.Component{
    constructor(props){
        super(props);
    }

    handleClick(){
        console.log(this);    //This gives complete information about the Button object and its properties 
    }

    handleSubmit(e){
        console.log(this);    //This one is undefined 
        e.preventDefault();
    }

    render(){
        return(
            <React.Fragment>
                <Form onSubmit={this.handleSubmit} >

                <Input type="submit" name="submit">Submit</Input>

                </Form>

                <Button name="butt1" onClick={this.handleClick}>Click</Button>
            </React.Fragment>
        );
    }
}

ReactDOM.render(<Test />, document.getElementById("root"));
<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reactstrap/8.4.1/reactstrap.min.js"></script>

I have checked that my question is different from this one because, in the provided question, the questioner wants to bind this for the component while in my question, I want this to refer to Form, just like it is referring to Button.

I am trying to find the reason for this different behavior, but I couldn't succeed. Can you people please give the reason for the same and suggest a way so that this inside handleSubmit starts referring to Form object?

EDIT This is what I believe the reason is, please confirm if it's correct

Since the handler for submitting was defined with the form, not for the submit button, this is why this is undefined, because submit was clicked, not form. I think I need some bubbling capture like thing here.

T.J. Crowder
  • 879,024
  • 165
  • 1,615
  • 1,639
Vipul Tyagi
  • 366
  • 1
  • 14
  • I am using webpack and babel to precompile, so I don't think I need the CDN's. – Vipul Tyagi Jun 13 '20 at 11:44
  • 1
    ^^ This is so that it's easier for someone else to see what's going on so that they can help you better. By the way, what is the reason you "want this to refer to Form, just like it is referring to Button." What exactly are you trying to accomplish? – goto1 Jun 13 '20 at 11:45
  • I was just testing what happenes when I don't bind a function to ``this``. So I got the above result, and my question is just that why ``this`` is undefined after form submit, while it is not undefined after a button click. I am looking to find the reason for this behaviour. – Vipul Tyagi Jun 13 '20 at 11:48

1 Answers1

5

It would appear to be a bug (or at least an inconsistency) in Reactstrap. With normal form and button elements, React consistently calls the handlers with no particular this value¹ (so in this example, since class code is always in strict mode, we see this set to undefined in the call):

class Test extends React.Component{
    constructor(props){
        super(props);
    }

    handleClick() {
        console.log(typeof this);
    }

    handleSubmit(e) {
        console.log(typeof this);
        e.preventDefault();
    }

    render(){
        return(
            <React.Fragment>
                <form onSubmit={this.handleSubmit} >

                <input type="submit" name="submit" value="Submit" />

                </form>

                <button type="button" name="butt1" onClick={this.handleClick}>Click</button>
            </React.Fragment>
        );
    }
}

ReactDOM.render(<Test />, document.getElementById("root"));
<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>

But I have read that this inside normal functions is resolved dynamically i.e. who called it.

That's incorrect. With normal functions and with methods, the value of this within the call is determined by the caller. So what I'm saying above is that React does one thing and Reactstrap does something else.

You may be thinking of the DOM and how it handles event handlers, which is different from both React and (apparently) Reactstrap. The DOM calls your handler with this set to the element the handler was attached to. So if you attach a handler to a button and the handler is a normal function or a method, this will refer to the button when the handler is called by the DOM. It's just something the DOM does. React doesn't. Reactstrap apparently varies depending on what handler it is or what kind of element it is (which is likely a bug).

More about this in this question's answers and in this old post on my anemic little blog.


In a comment you've said:

One more thing that I want to ask is how to use your code without strict mode, because it doesn't specify strict mode to be used

There are two reasons the code in that example is in strict mode:

  1. As I mentioned earlier in the answer, it's inside a class construct. Code inside class is always strict. (So is code in a JavaScript module.)
  2. It's using Babel to transpile the JSX, and by default Babel turns on strict mode in the code it outputs.

So to see what happens in loose mode, you'd have to not use a class (easy enough) and not use Babel, or at least tell Babel not to turn on strict mode. I don't know if there's a way to tell Babel not to use strict mode in Stack Snippets (there is a way when you're using it in the real world), but fortunately, you don't have to use JSX with React, it's just convenient. You can use React.createElement directly:

const {createElement} = React;

function Test() {
    function handleClick() {
        console.log(this === window);
    }

    function handleSubmit(e) {
        console.log(this === window);
        e.preventDefault();
    }

    return createElement(React.Fragment, {
        children: [
            createElement("form", {
                onSubmit: handleSubmit,
                children: [
                    createElement("input", {
                        type: "submit",
                        name: "submit",
                        value: "Submit"
                    })
                ]
            }),
            createElement("button", {
                type: "button",
                name: "butt1",
                onClick: handleClick,
                children: ["Click"]
            })
        ]
    });
}

ReactDOM.render(createElement(Test), document.getElementById("root"));
<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>

Note that this in the callbacks is now the global this, because when you call a normal function or method with no particular this value in loose mode, this within the callback is the global this (window on browsers).


¹ That's even covered by React's event documentation, although what it actually says is that this will be undefined. That's only true in strict mode. It is, technically, possible — but not a good idea — to use React without using strict mode.

T.J. Crowder
  • 879,024
  • 165
  • 1,615
  • 1,639
  • So should I test this thing with normal forms and buttons? – Vipul Tyagi Jun 13 '20 at 11:51
  • 1
    @VIPULTYAGI look at the code that he included - it works as expected. – goto1 Jun 13 '20 at 11:55
  • @VIPULTYAGI - It depends on what you want to test, what React does or what Reactstrap does. React's [event documentation](https://reactjs.org/docs/handling-events.html) says that it will call the handlers with no particular `this`. (What it actually says is that `this` will be `undefined`, but that's only true in strict mode. It is, technically, possible -- but not a good idea - to use React w/o using strict mode.) I don't know Reactstrap, but unless they make a guarantee of some kind in their documentation, I wouldn't rely on the meaning of `this` in those event handlers unless you bind them. – T.J. Crowder Jun 13 '20 at 11:56
  • I have tested the code you provided and now both ``this`` are undefined, as you said. But I have read that ``this`` inside normal functions is resolved dynamically i.e. who called it. So with this logic, this should refer to the parent object i.e. a form or a button. – Vipul Tyagi Jun 13 '20 at 12:07
  • So it means that there is a difference in the working of react and reactstrap, because in my code, button's this was not undefined. – Vipul Tyagi Jun 13 '20 at 12:11
  • 1
    @VIPULTYAGI - With normal functions and with methods, the value of `this` within the call is determined by the caller. React is different from the DOM. React calls handlers without setting any `this` value, so in strict mode `this` within the call is `undefined`. The DOM calls handlers with `this` set to the element the event handler was attached to. It's just a fundamental difference in these two different things calling your code. React does it one way, the DOM does it a different way. – T.J. Crowder Jun 13 '20 at 12:12
  • @VIPULTYAGI - Yes, that's what I'm saying above. Reactstrap is apparently calling your submit handler with no particular `this` value, but calling your click handler with `this` set to the button (like the DOM does). Each of these -- React, Reactstrap, the DOM -- are different things calling your functions, and they do so in different ways. – T.J. Crowder Jun 13 '20 at 12:13
  • So should I consider this behaviour of reactstrap a bug?? – Vipul Tyagi Jun 13 '20 at 12:18
  • 1
    @VIPULTYAGI - To me, the inconsistency seems likely to be unintentional. But unless the Reactstrap documentation says what `this` will be during a call, it isn't necessarily a *bug*. I haven't read their documentation enough to know whether they make any guarantees about `this` or what those guarantees are. If you do and don't find anything, you might raise an issue in their issue tracker (probably without using the word "bug"), pointing out the inconsistency and asking whether the behavior is intentional and/or what guarantees there are. – T.J. Crowder Jun 13 '20 at 12:21
  • One more thing that I want to ask is how to use your code without strict mode, because it doesn't specify strict mode to be used. – Vipul Tyagi Jun 13 '20 at 12:24
  • @VIPULTYAGI - I've added an answer to that question to the answer above. – T.J. Crowder Jun 13 '20 at 12:48
  • What I actually trying to do was that I thought ``this`` would refer to form and then I could fetch all the input values using this ``this``. I was wrong. Now I will use ``ref`` to fetch input values. – Vipul Tyagi Jun 13 '20 at 13:18
  • 1
    @VIPULTYAGI the **best** way to go about this is to use exactly what's in the documentation - https://reactjs.org/docs/forms.html. You **do not** need `ref`s or `this` (other than for binding your class methods, or just use the `arrow syntax`). – goto1 Jun 13 '20 at 16:22