Paul Paul - 2 months ago 33
React JSX Question

React router not redirecting on server

I have a high order component that checks if a user is authenticated and if not will redirect to a different url.

Its an isomorphic app and this works client-side but if I turn JS off, the server doesn't redirect.

if (!this.props.authenticated) {
this.context.router.push('/');
}


I can hit this statement on the server and
this.context.router
is returning but nothing happens.

Full Component:

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

export default function (ComposedComponent) {
class Authentication extends Component {
static contextTypes = {
router: React.PropTypes.object
}

static propTypes = {
authenticated: PropTypes.bool
}

componentWillMount() {
if (!this.props.authenticated) {
this.context.router.push('/');
}
}

componentWillUpdate(nextProps) {
if (!nextProps.authenticated) {
this.context.router.push('/');
}
}

render() {
return <ComposedComponent {...this.props} />;
}
}

function mapStateToProps(state) {
return { authenticated: state.auth.authenticated };
}

return connect(mapStateToProps)(Authentication);
}


and this component is reached via the routes file:

export default (
<Route path='/' component={App}>
<IndexRoute component={Welcome} />
<Route path='/feature' component={requireAuth(Feature)} />
<Route path='/signin' component={Signin} />
<Route path='/signout' component={Signout} />
<Route path='/signup' component={Signup} />
</Route>
);


This is the server render code:

import { RouterContext, match } from 'react-router';
import { Provider } from 'react-redux';
import { renderToString } from 'react-dom/server';
import React from 'react';
import reactCookie from 'react-cookie';

import configureStore from '../../shared/store';
import routes from '../../shared/routes';
import assets from '../../public/assets.json';

import { AUTH_USER } from '../../shared/actions/types';

export default function (req, res) {
match({ routes, location: req.url }, (error, redirectLocation, renderProps) => {
if (error) return res.status(500).send(error.message);
if (redirectLocation) return res.redirect(302, redirectLocation.pathname + redirectLocation.search);
if (!renderProps) return res.status(404).send('Page not found');

const store = configureStore();

// use cookies to retrieve token
const unplug = reactCookie.plugToRequest(req, res); // eslint-disable-line no-unused-vars
const token = reactCookie.load('token');

// if we have a token, consider the user to be signed in
if (token) {
// we need to update application state
store.dispatch({ type: AUTH_USER });
}

const content = renderToString(
<Provider store={store}>
<RouterContext {...renderProps} />
</Provider>
);

const initialState = JSON.stringify(store.getState());

return res.render('index', { content, assets, initialState });
});
}

Answer

Can you share your server rendering code? It probably needs to look something like https://github.com/ReactTraining/react-router/blob/master/docs/guides/ServerRendering.md , where you're checking the redirectLocation flag. That redirectLocation can be returned by using an onEnter hook instead of a wrapper component. The onEnter hook is documented here.

Update from comment below: Ok so your server code is correctly checking redirectLocation, but the routing code needs to be using an onEnter hook to set that redirectLocation properly. Like so:

Ok, so you're correctly honoring redirectLocation in the server code, but redirectLocation is only populated using an onEnter hook, like so:

const userIsInATeam = (nextState, replace) => {
  if ( !isAuth ) {
    replace('/')
  }
}

<Route path="/users/:userId/teams" onEnter={userIsInATeam} />

I think nextState should have the auth prop you're looking for in it to check auth.