1
votes

I have a react/redux app, using 'react-router-dom' for routing. I am using the following in my App.js (tags are react-bootstrap, in order to provide private routes. Expected behaviour is to be redirected to /login if, and only if, the user is not logged in (based on the presence of a cookie). Actual behaviour is that the app immediately redirects to /login, even when the user is logged in.

class MainContainer extends Component {
  constructor(props) {
    super(props);
    this.props.loadAuthCookie();
  }

  PrivateRoute = ({ component: ChildComponent, ...rest }) => {
    return (
      <Route
        {...rest}
        render={(props) => {
          if (!this.props.auth.loggedIn && !this.props.auth.authPending) {
            return <Redirect to="/login" />;
          } else {
            return <ChildComponent {...props} />;
          }
        }}
      />
    );
  };

  render() {
    const { PrivateRoute } = this;
    return (
      <Router>
        <Container fluid id="root">
          <Header />
          <Switch>
            <Row className="pageContainer">
              <Col>
                <PrivateRoute exact path="/" component={HomeScreen} />
                <Route
                  exact
                  path="/clusters/"
                  component={clusterOverviewScreen}
                />
                <Route exact path="/login" component={LoginPage} />
              </Col>
            </Row>
          </Switch>
        </Container>
      </Router>
    );
  }
}

const mapStateToProps = (state) => {
  return {
    auth: state.auth,
  };
};

const mapDispatchToProps = (dispatch) => {
  return {
    loadAuthCookie: () => {
      return dispatch(loadAuthCookie());
    },
  };
};

const RootContainer = connect(
  mapStateToProps,
  mapDispatchToProps
)(MainContainer);

export default class App extends Component {
  render() {
    return (
      <Provider store={store}>
        <RootContainer />
      </Provider>
    );
  }
}

The function this.props.loadAuthCookie dispatches a redux action which pulls the cookie that contains the auth token (if present), and puts it into the state. Initial state is

{
    "authPending": false
    "loggedIn": false
    "authToken": null
}

Which becomes:

{
    "authPending": true
    "loggedIn": false
    "authToken": null
}

and finally:

{
    "authPending": false
    "loggedIn": true
    "authToken": TOKEN
}

I am relatively new to react. My guess is that by the time the PrivateRoute function runs, the redux action has not yet set the state, and therefore, the PrivateRoute component redirects to the login page. This guess is based on logging props.auth.loggedIn in the private route - this returns false, but by the time the login page to which I am redirected has finished loading, the component props state that this.props.loggedIn is true.I am not sure how to fix this, though.

Edit: loadAuthCookie action:

export const loadAuthCookie = () => (dispatch) => {
  dispatch(AuthReducer.setAuthPending());
  try {
    const cookie = Cookies.get("token");
    if (cookie) {
      dispatch(AuthReducer.setLoginSuccess(cookie));
    }
  } catch (err) {
    console.log(err);
    dispatch(AuthReducer.setLogout());
  }
};
2
Can you also share your loadAuthCookie action function? - Hyetigran
Try putting this.props.loadAuthCookie(); as the first line of render instead of being in the constructor; before const { PrivateRoute } = this; - Tony
@Hyetigran Done. That suggestion seems to cause a Maximum depth exceeded error - alex_halford
That's because the function is called in the constructor, which then gets re-rendered and calls the function again. To call the function only once, you should use the componentDidMount lifecycle method componentDidMount() {this.props.loadAuthCookie()} - Hyetigran

2 Answers

1
votes

The PrivateRoute looks like it's implemented incorrectly.

It should be designed to 'wait' until either authPending becomes false or a loggedIn becomes true.

But in reality it requires both of them to be true for it to NOT redirect to login. So implement to 'wait' if authPending: true until loggedIn:true.

Here is table of the outcomes.

Possible outcomes

Also don't use conditions like !loggedIn && !authPending where readability is terrible especially when you've been working hours on it. I've over looked this because I can't be asked to comprehend what I'm seeing.

<Route
    {...rest}
    render={(props) => {
        if (this.props.auth.loggedIn) {
            return <ChildComponent {...props} />;
        } else if (this.props.auth.authPending) {
            return <LoadingScreen/> // Or could simply div with text. Not different component
            // Then state updates after x seconds and re-renders
        } else {
            return <Redirect to="/login" />;
        }
    }}
/>

Edit: I actually got the logic wrong, either authPending & loggedIn can be true to redirect to the child element because of the ! inverse operator.

function test(first, second) {
    if (!first && !second) {
        console.log('login');
    } else {
        console.log('child');
        
    }
}

test(true, true);
test(false,false);
test(true, false);
test(false, true);

output:

child
login
child
child

See still can't get my head around!;

0
votes

I think Tony is right; you probably need to call this.props.loadAuthCookie(); later in the lifecycle of this component.

The constructor is run when (and only when) the component is instantiated; as opposed to when it's mounted or rendered. The constructor is usually called once, and before mounting or rendering.

For this reason, calling this.props.loadAuthCookie() might work inside of componentDidMount as well, but I'd go with Tony's suggestion and try it in the render() function.