Regarding Redux ...
Jacka's answer helped me alot, but i had a difficult time connecting the PrivateRoute
component to redux. Furthermore i wanted to abstract the resulting Route
component to work e.g. as a LoggedInRoute
, NotLoggedInRoute
or in general a Route
which presents it's component if a condition is fulfilled or redirects to a specified location otherwise:
Note: Written with redux
4, react-router-dom
4 and typescript 2.9
.
import * as H from 'history';
import * as React from 'react';
import { connect, MapStateToPropsParam } from 'react-redux';
import { Redirect, Route, RouteComponentProps, RouteProps } from 'react-router';
export interface ConditionalRouteProps extends RouteProps {
routeCondition: boolean;
redirectTo: H.LocationDescriptor;
}
export class ConditionalRoute extends React.Component<ConditionalRouteProps> {
public render() {
// Extract RouteProps without component property to rest.
const { component: Component, routeCondition, redirectTo, ...rest } = this.props;
return <Route {...rest} render={this.renderFn} />
}
private renderFn = (renderProps: RouteComponentProps<any>) => {
if (this.props.routeCondition) {
const { component: Component } = this.props; // JSX accepts only upprcase.
if (!Component) {
return null;
}
return <Component {...renderProps} />
}
return <Redirect to={this.props.redirectTo} />;
};
}
export function connectConditionalRoute<S>(mapStateToProps: MapStateToPropsParam<ConditionalRouteProps, RouteProps, S>) {
return connect<ConditionalRouteProps, {}, RouteProps, S>(mapStateToProps)(ConditionalRoute);
}
You can either use the ConditionalRoute
component without connecting it and use your component's local state, e.g.:
interface RootState {
loggedIn: boolean;
}
export class Root extends React.Component<RootProps, RootState> {
/* skipped initialState and setState(...) calls */
public render() {
return (
<Switch>
<ConditionalRoute
path="/todos"
component={TodoPage}
routeCondition={this.state.loggedIn}
redirectTo="/login" />
<ConditionalRoute
path="/login"
component={LoginPage}
routeCondition={!this.state.loggedIn}
redirectTo="/" />
<Redirect to="/todos" />
</Switch>
);
}
}
Or use the utility function connectConditionalRoute<S>(...)
to use your redux store:
const loginRoute = '/login';
const todosRoute = '/todos';
const LoggedInRoute = connectConditionalRoute<RootState>(state => ({
redirectTo: loginRoute,
routeCondition: state.isLoggedIn,
}));
const NotLoggedInRoute = connectConditionalRoute<RootState>(state => ({
redirectTo: todosRoute,
routeCondition: !state.isLoggedIn
}));
const Root: React.SFC = () => (
<Switch>
<LoggedInRoute path="/todos" component={TodoPage} />
<NotLoggedInRoute path="/login" component={LoginPage} />
<Redirect to="/todos" />
</Switch>
);
The behaviour in the provided sample: Unauthorized users visit /todos
, get redirected to /login
, authorized users visit /login
, get redirected to /todos
. Whenever the redux store's isLoggedIn
changes, the connected components are updated and redirect the user automatically.