0

I refactored a React class of mine from ES5 to ES6 and now when I click a button, which calls this.state.dispatch(logIn(this.state.logIn)), the initial this at the beginning of the line is null. Super weird.

Here's my class:

class Home extends Component {
    constructor(props) {
        super(props);

        this.state = {
            panelIsOpen: false,
            registration: {},
            login: {},
        };
    }

    signUp() {
        this.props.dispatch(signUp(this.state.registration));

        this.setState({
            registration: {},
        });
    }

    logIn() {
        debugger; // this is `null here`
        this.props.dispatch(logIn(this.state.login));

        this.setState({
            login: {},
        });
    }

    togglePanel(e) {
        this.setState({ panelIsOpen: !this.state.panelIsOpen} );
    }

    render() {
        const {elements} = this.props;
        const {registration, login} = this.state;

        return (
            // some stuff 
        );
    }
};

Home.propTypes = {
    elements: React.PropTypes.array,
    dispatch: React.PropTypes.func,
    user: React.PropTypes.object,
};

const mapStateToProps = ({elements, auth}) => {
    return {
        elements: getElementsByKeyName(elements, 'visibleElements'),
        user: getLoggedInUser(auth),
    };
};

Home = DragDropContext(HTML5Backend)(Home);
export default connect(mapStateToProps)(Home);

Clicking the log in button calls the login function but for some reason here, this is null

Thanks for taking a look

Zack Shapiro
  • 6,648
  • 17
  • 83
  • 151

2 Answers2

8

React doesn't bind the context of methods that are added to ES6 classes unless they're part of the standard React lifecycle (componentWillReceiveProps, componentDidMount, and so on).

What this means is that you need to either manually bind the value of this for your signUp, logIn and togglePanel methods, or else declare them as arrow functions, which inherit the parent context.

1.

constructor(props) {
  super(props);
  this.signUp = this.signUp.bind(this);
  this.logIn = this.logIn.bind(this);
  this.togglePanel = this.togglePanel.bind(this);

  this.state = {
    panelIsOpen: false,
    registration: {},
    login: {},
  }

or

2.

signUp = () => {
  this.props.dispatch(signUp(this.state.registration));

  this.setState({
    registration: {},
  });
}

// the same for logIn and togglePanel

For reference, see the docs.

richsilv
  • 7,993
  • 1
  • 23
  • 29
2

This (no pun intended) has to do with how functions are bound in ES6. If you're passing your method as a prop to another component, the context it's run in isn't guaranteed to be the right context (unless you bind it first). Here's an article with a long, but interesting read on it: http://reactkungfu.com/2015/07/why-and-how-to-bind-methods-in-your-react-component-classes/

In short, you have a couple options:

  1. (pretty common/popular) bind your methods to this in the constructor. You might not need to bind all your methods - depends how they're being used.

    constructor(props) {
        super(props);
        this.state = {
            panelIsOpen: false,
            registration: {},
            login: {},
        };
        this.signUp = this.signUp.bind(this);
        this.signUp = this.logIn.bind(this);
        this.togglePannel = this.togglePannel.bind(this);
    } 
    
  2. Define your methods as arrow functions which will bind it to the current scope for you - no need to bind them in the constructor:

    class Home extends Component {
        // ...
    
        signUp = () => {
            this.props.dispatch(signUp(this.state.registration));
    
            this.setState({
                registration: {},
            });
        }
    
        // ...
    }
    
James Ganong
  • 1,160
  • 8
  • 15