Myron McTyer
Myron McTyer

Reputation: 41

Component rendering too early

I am trying to create a PrivateRoute(HOC) to test if a user has been authenticated(check is 'auth' exist in redux store) before sending them to the actual route. The issue is the privateroute finishes before my auth shows up in redux store.

The console.log runs twice, the first time, auth doesnt appear in the store, but it does the second time, but by that time, its already routed the user to the login screen.... How can I give enough time for the fetch to finish? I know how to do this condition when I simply want to display something conditionally(like login/logout buttons) but this same approach does not work when trying to conditionally route someone.

import React, { Component } from 'react'
import { connect } from 'react-redux'
import { Route } from 'react-router-dom'

class PrivateRoute extends Component {
  render() {
    const { component: Component, ...rest } = this.props
    console.log(this.props)

    return (
      <Route {...rest} render={(props) => (props.auth ? <Component {...props} /> : props.history.push('/login'))} />
    )
  }
}

function mapStateToProps({ auth }) {
  return { auth }
}

export default connect(mapStateToProps)(PrivateRoute)

Upvotes: 4

Views: 557

Answers (2)

Dilshan
Dilshan

Reputation: 3001

When your action creator return the token, you need to store it in localStorage. and then you can createstore like below,

const store = createStore(
    reducers,
    { auth: { authenticated : localStorage.getItem('token') }},
    applyMiddleware(reduxThunk)
)

if user already logged in then token will be there. and initial state will set the token in store so you no need to call any action creator.

Now you need to secure your components by checking if user is logged in or not. Here's the HOC for do that,

import React, { Component } from 'react';
import { connect } from 'react-redux';

export default ChildComponent => {
  class ComposedComponent extends Component {

    componentDidMount() {
      this.shouldNavigateAway();
    }

    componentDidUpdate() {
      this.shouldNavigateAway();
    }
    shouldNavigateAway() {
      if (!this.props.auth) {
        this.props.history.push('/');
      }
    }
    render() {
      return <ChildComponent {...this.props} />;
    }
  }
  function mapStateToProps(state) {
    return { auth: state.auth.authenticated };
  }
  return connect(mapStateToProps)(ComposedComponent);
};

Upvotes: 1

Ivan Burnaev
Ivan Burnaev

Reputation: 2730

I didn't use redux here, but I think you would get the main point. Hope this will help and feel free to ask any questions!

import React, { Component } from "react";
import { BrowserRouter, Route, Switch, Redirect } from "react-router-dom";

import Dashboard from "path/to/pages/Dashboard";

class App extends Component {
  state = {
    isLoggedIn: null,
  };

  componentDidMount () {
    // to survive F5
    // when page is refreshed all your in-memory stuff
    // is gone
    this.setState({ isLoggedIn: !!localStorage.getItem("sessionID") });
  }

  render () {
    return (
      <BrowserRouter>
        <Switch>
          <PrivateRoute
            path="/dashboard"
            component={Dashboard}
            isLoggedIn={this.state.isLoggedIn}
          />
          <Route path="/login" component={Login} />

          {/* if no url was matched -> goto login page */}
          <Redirect to="/login" />
        </Switch>
      </BrowserRouter>
    );
  }
}

class PrivateRoute extends Component {
  render () {
    const { component: Component, isLoggedIn, ...rest } = this.props;

    return (
      <Route
        {...rest}
        render={props =>
          isLoggedIn ? <Component {...props} /> : <Redirect to="/login" />
        }
      />
    );
  }
}

class Login extends Component {
  state = {
    login: "",
    password: "",
    sessionID: null,
  };

  componentDidMount () {
    localStorage.removeItem("sessionID");
  }

  handleFormSubmit = () => {
    fetch({
      url: "/my-app/auth",
      method: "post",
      body: JSON.strigify(this.state),
    })
      .then(response => response.json())
      .then(data => {
        localStorage.setItem("sessionID", data.ID);

        this.setState({ sessionID: data.ID });
      })
      .catch(e => {
        // error handling stuff
      });
  };

  render () {
    const { sessionID } = this.state;

    if (sessionID) {
      return <Redirect to="/" />;
    }

    return <div>{/* login form with it's logic */}</div>;
  }
}

Upvotes: 1

Related Questions