3

I am trying to implement a feature when the user logs into the app and they have not completed their profile, they should be redirected to a certain URL, So everywhere they try to go (except logout) they should be redirected to the complete-profile URL.

I'm handling routing with react-router-dom package.

App.js

class App extends Component {
  async componentDidMount() {
    const logged = isLoggedIn();
    if (logged) {
      const completed = await hasCompleted();
      if (!completed) this.props.history.replace("/complete-profile"); //error showing here
    }
  }

  render() {
    return (
      <React.Fragment>
        <NavBar />
        <main className="container">
          <Switch>
            <Route path="/complete-profile" component={CompleteProfile} />
            <Route path="/login" component={Login} />
            <Route path="/register" component={Register} />
            <Route path="/logout" component={Logout} />
            <Route path="/" exact component={Home} />
          </Switch>
        </main>
      </React.Fragment>
    );
  }
}

So basically what I'm trying to do here in componentDidMount method is: I first check if the user is logged in. Then check if the user has completed their profile, and if it's not completed then it should redirect to /complete-profile URL.

But with this method I'm facing an error with this.props.history.replace because it does not take any props when this method is getting called I guess, and the error that is showing is this:

Unhandled Rejection (TypeError): Cannot read property 'replace' of undefined

Which is the proper way to implement this one?

Because I dont think that I should implement these 4 lines of code checking for completed profile in every single component.

Roy Scheffers
  • 3,832
  • 11
  • 31
  • 36
Ertan Hasani
  • 797
  • 1
  • 18
  • 37
  • You can try this https://stackoverflow.com/questions/37516919/react-router-getting-this-props-location-in-child-components – Artur Gevorgyan Oct 16 '18 at 21:26
  • There is a `withRouter` HOC in `react-router-dom`. What if you wrap your `App` with it before export ? Does it make your code work ? Add those two lines : `import { withRouter } from 'react-router-dom'` `export default withRouter(App)` – Geoffrey H Oct 16 '18 at 22:13

4 Answers4

2

In the App component the history prop is not available (undefined) because the following props:

  • history
  • location
  • match

are passed from Route component to its children (CompleteProfile, Home,....). So you can not use them in the App component.

Instead you can create your own Route component:

 class CompleteProfile extends Component {
     state = { 
         completed: false
     };
     async componentDidMount() {
        const logged = isLoggedIn();
        if (logged) {
          const completed = await hasCompleted();
          this.setState({completed});              
         //error showing here
    }
  }
     render() {
       const { component: Component, ...rest } = this.props;
       const { completed } = this.state;

       return (
       <Route {...rest} render={(props) => (
         completed
          ? <Component {...props} />
          : <Redirect to='/complete-profile' />
       )} />
    )
  }
}

and use it instead of Route like this:

<CompleteProfile path='/' exact component={Home} />

This is the general idea you can refactor the code as you want.

Tarek Essam
  • 3,602
  • 2
  • 12
  • 21
1

Have a look at the description of the Route component. You'll see that three props are injected for each component that's rendered using <Route /> in your code for example. <Route path="/login" component={Login} /> These props are match location history

In your code App is not rendered using Route. Because of this, you don't have access to these three injected props. That's the reason why you get the error that history is undefined.

To redirect the user, use something like the below instead, where you conditionally render a redirect or the UI depending if data is present.

class App extends Component {
  constructor(props){
    super(props);
    this.state = {
      fetchingStatus: true,
      completed: false
    };
  }

  async componentDidMount() {
    const logged = isLoggedIn();
    if (logged) {
      const completed = await hasCompleted();
      if (!completed) this.setState({ fetchingStatus: false, completed: true })
    }
  }

  render() {
    const { fetchingStatus, completed } = this.state;
    // Render nothing, or a spinner as loading indicator
    if (fetchingStatus) return null; // waiting for details...
    // if data was fetched and registeration not completed, render redirect.
    if (!fetchingStatus && !completed) return <Redirect to="/complete-profile" />
    // Render switch and nav.
    return (
      <React.Fragment>
        <NavBar />
        <main className="container">
          <Switch>
            <Route path="/complete-profile" component={CompleteProfile} />
            <Route path="/login" component={Login} />
            <Route path="/register" component={Register} />
            <Route path="/logout" component={Logout} />
            <Route path="/" exact component={Home} />
          </Switch>
        </main>
      </React.Fragment>
    );
  }
}

More on React Router's redirect component here

Roy Scheffers
  • 3,832
  • 11
  • 31
  • 36
  • Well this looks great of what I'm looking for. But there's one little issue. When it redirects to `/complete-profile` still completed is false, so it tries to redirect all the time to that url, its like an infinite loop. How can i access the current url so it wont redirect it ones it goes to `/complete-profile` – Ertan Hasani Oct 17 '18 at 17:33
0

This sort of TypeError becomes intimately familiar when working with React. It occurs when you attempt to use methods from an object that is "undefined".

You can ensure that this.props.history is not undefined like this:

 if (this.props.history && !completed)
   this.props.history.replace("/complete-profile"); //error showing here

The npm package 'Lodash' may also be of use in avoiding these kinds of complications.

Zack
  • 294
  • 1
  • 10
  • Yep, but what's the point of it if `this.props.history` is null? I need that property `history` in order to change the url. – Ertan Hasani Oct 16 '18 at 20:28
  • React modules often go through multiple cycles as a page loads. Implementing sanity-testing is frequently necessary to contend with incomplete information such as undefined props. – Zack Oct 16 '18 at 20:37
0

This just a cheap trick. If you want to go to any url (including react or any other front end framework), just do :

window.location.replace(str)

str can be absolute path like "https://abcd.com", or relative-path like ("/complete-profile" which will become baseurl + "/complete-profile")

if (logged) {
      const completed = await hasCompleted();
      if (!completed) window.location.replace("/complete-profile"); //error showing here
}

Also @zacks method also works

Prajval M
  • 2,298
  • 11
  • 32