Confusion with meteor and react router

Hey All,

I’m using React and React Router v4.2 with my Meteor app. I’m trying to get javascript to push my history after an account gets registered.

Though I keep getting this error:
Exception in delivering result of invoking ‘createUser’: TypeError: Cannot read property ‘history’ of undefined

Here is my main app with the Router setup:

App.jsx

import React from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';

export default class Layout extends React.Component {
  render() {
    return (
      <Router>
        <div>
          <Header />
          <Switch>
            <Route exact path="/" render={(props) => <Home {...props} /> } />
            <Route exact path='/register' render={(props) => <RegisterForm {...props} /> } />
            <Route path = '/dashboard' render={(props) => <Home {...props} /> } />
          </Switch>
        </div>
     </Router>

And then the Register form:

RegisterForm.jsx

import { Meteor } from 'meteor/meteor';
import React from 'react';
import ReactDOM from 'react-dom';


export default class RegisterForm extends React.Component {
  constructor(props) {
        super(props)
  }  
  
  createUser(e){
    e.preventDefault();
    const email = $('#emailReg').val();
    const password = $('#pwdReg').val().trim();
    
    Accounts.createUser({ email: email, password: password }, 
      function(error){
        if (error) { console.log ("there was an error: " + error); }
        else { 
          console.log("Account Created with email " + email);
          this.props.history.push("/dashboard");
        }
    });
  }
  
  render(){
    return(          
          <form role="form" id="registerForm" onSubmit={this.createUser}>...etc...</form>
         )
     }
}

The error occurs when the form is submitted. All i want to do for now is to figure out why I can’t get history to be defined and change the component as expected. I’ll worry about protecting components later.

What am I doing wrong?

History is not part of the RegisterForm props. You will have to import it. Try this:

import history from 'history';
    Accounts.createUser({ email: email, password: password }, 
      function(error){
        if (error) { console.log ("there was an error: " + error); }
        else { 
          console.log("Account Created with email " + email);
          history.push("/dashboard");
        }
    });

I think the reason is the scope of this

In your case, this is called inside of the following snippet

      function(error){
        if (error) { console.log ("there was an error: " + error); }
        else { 
          console.log("Account Created with email " + email);
          this.props.history.push("/dashboard");
        }
    }

Therefore this refers to window (in client) or global (in node), if the code is not in strict mode
(Please take a look behaviour of this in MDN for more details)

There are two ways that you can overcome this situation.

1) Utilize arrow functions

Accounts.createUser({ email: email, password: password }, 
      (error) => {
        if (error) { console.log ("there was an error: " + error); }
        else { 
          console.log("Account Created with email " + email);
          this.props.history.push("/dashboard");
        }
    });

In this case, this should refer to your component

2) Define a variable to store this.props.history outside of the code block
That would be something like the following

createUser(e){
    e.preventDefault();
    const email = $('#emailReg').val();
    const password = $('#pwdReg').val().trim();
    
    const hist = this.props.history //define the variable outside
    Accounts.createUser({ email: email, password: password }, 
      function(error){
        if (error) { console.log ("there was an error: " + error); }
        else { 
          console.log("Account Created with email " + email);
          hist.push("/dashboard"); //utilize the variable
        }
    });
  }

Either of these two should do the trick for you.

However there is an additional thing to do, which is binding this to your method createUser

There are also 3 ways to do that

1) Bind this in constructor level

 constructor(props) {
        super(props)
        this.createUser = this.createUser.bind(this)
  }

2) Bind in render level

render(){
    return(          
          <form role="form" id="registerForm" onSubmit={this.createUser.bind(this)}>...etc...</form>
         )
     }

3) Or utilize arrow function when defining the method
By this way, you don’t have to explicitly bind this

createUser = (e) => {
/* Your code */
}

I personally prefer, arrow function.

I hope this helps to solve your problem.
You can also take a look at this post to for binding this

3 Likes

That was it. I needed to bind the component’s “this” to the createUser method in the constructor. Ironically, right after I’d posted this, I realized that the scope of “this” was probably what was confusing things, but i suppose I didn’t have a very strong understanding of how “this” and how binding work in React yet.

I appreciate your help.Thank you very much.

1 Like