andy
andy

Reputation: 81

How can I reroute based on data from my API without component flashing - React/Redux

I am writing a small to-do app and am having problems with rerouting a user based on data from the backend. The app is using:

When a user logs in, a User model property called isLoggedIn is set to true. This value is used to redirect the user to the LandingPage component where they can create new to-dos. This is fine but the problem is that if the landing page is refreshed, the value for isLoggedIn has not arrived from the backend before LandingPage renders. This causes the user to be rerouted to the login page, then the data arrives and then they are rerouted to the landing page again.

The login page "flashing" up is extremely annoying and I can't find any way of getting round it. I tried storing a boolean value isLoggedIn in localStorage and this worked great but surely that defeats the point of using a server-side session?

Here are some snippets from my code, thanks

App.js

import { getUser } from "./redux/actions/auth";

const App = withRouter(({ user, data }) => {
    useEffect(() => {
        store.dispatch(getUser()); // user data loaded every time app re-renders
    }, []);

    return (
        <Provider store={store}>
            <Router>
                <div className='my-app'>
                    <Switch>
                        <Route exact path='/login' component={Login} />
                        <Private
                            isLoggedIn={isLoggedIn}
                            path='/landing'
                            component={LandingPage}
                        />
                    </Switch>
                </div>
            </Router>
        </Provider>
    );

const Private = ({ isLoggedIn, component: Component, ...rest }) => {
    return (
        <Route
            {...rest}
            render={props =>
                isLoggedIn ? (
                    <Component {...props} />
                ) : (
                    // I think the problem is here - because isLoggedIn is not available from the backend 
                    // call yet, so login page gets rendered before the data arrives
                    <Redirect to='/login' {...props} /> 
                )
            }
        />
    );
};

Login.js

render() {
    const { user } = this.props;

    if (user.isLoggedIn) return <Redirect to='/landing' />; // user sent to landing if they are logged in

    return (... login form jsx)
}

Login.propTypes = {
    user: PropTypes.object
};

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

export default connect(mapStateToProps)(Login);

getUser.js

export const getUser = () => async dispatch => {
    try {
        const response = await axios.get("/api/user");
        dispatch({
            type: LOADED_USER,
            payload: response.data
        });
    } catch (error) {
        dispatch({
            type: LOAD_USER_FAILED
        });
    }
};

getUserReducer.js

const initialState = {
    user: {}
};

export default function(state = initialState, action) {
    const { type, payload } = action;
    switch (type) {
        case LOGIN_SUCCEEDED:
        case LOADED_USER:
            return {
                ...state,
                user: payload
            };
        case LOGIN_FAILED:
        case LOAD_USER_FAILED:
            return {
                ...state,
                user: {}
            };
        default:
            return state;
    }
}

Upvotes: 1

Views: 95

Answers (1)

Dan Fletcher
Dan Fletcher

Reputation: 1238

So there's a few ways of doing this. One way would be to create an authenticated route that's only purpose is to validate the session cookie like /me for example.

If the user has logged in previously, the browser should have a valid session as a cookie. This would mean that /me can be requested with a 200. If the session is expired then you'll get a 401. In the case of a 401 you just redirect back to the login page.

There's lots of ways to communicate that this is happening to the user as well. One way is to have some state in local storage like you're already doing and just make the assumption that the user is allowed to navigate to the landing page. You wouldn't be loading any sensitive data anyway since the data should be behind authenticated endpoints.

In the case of any 401s you can redirect to login, or show an error to the user saying something like "It appears you're session has expired, click here to log back in.".

Upvotes: 1

Related Questions