I found this (reacttraining.com) site, which explains react-router with some examples. But I am not be able to do this with a typescript class. What I want to do is extend the Route class to build my own one. Right now I want to implement it in typescript for authentication as in the following example from the site.
const PrivateRoute = ({ component, ...rest }) => (
<Route {...rest} render={props => (
fakeAuth.isAuthenticated ? (
React.createElement(component, props)
) : (
<Redirect to={{
pathname: '/login',
state: { from: props.location }
}}/>
)
)}/>
)
I searched a lot, but couldn't find a site that explains the function to implement and which typed properties to call to nested routes. An ES6 class will be also helpful, thank you.
Here's my best shot so far, although there's still one any
remaining :)
import * as React from "react"
import {Redirect, Route, RouteComponentProps, RouteProps} from "react-router-dom"
type RouteComponent = React.StatelessComponent<RouteComponentProps<{}>> | React.ComponentClass<any>
const AUTHENTICATED = false // TODO: implement authentication logic
export const PrivateRoute: React.StatelessComponent<RouteProps> = ({component, ...rest}) => {
const renderFn = (Component?: RouteComponent) => (props: RouteProps) => {
if (!Component) {
return null
}
if (AUTHENTICATED) {
return <Component {...props} />
}
const redirectProps = {
to: {
pathname: "/auth/sign-in",
state: {from: props.location},
},
}
return <Redirect {...redirectProps} />
}
return <Route {...rest} render={renderFn(component)} />
}
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.
You could use any
.
const PrivateRoute = ({component: Component, ...rest }: any) => (
<Route {...rest} render={PrivateRender(Component)} />
);
const PrivateRender = (Component: any) => {
return (props: any) => {
return <Component {...props}/>;
};
};
I was looking for same thing. The question is old but maybe someone is still looking for it.
Here is what I come up with (All types properly used from react-router 4):
interface PrivateRouteProps extends RouteProps {
component: React.ComponentType<RouteComponentProps<any>> | React.ComponentType<any>
}
type RenderComponent = (props: RouteComponentProps<any>) => React.ReactNode;
export class PrivateRoute extends Route<PrivateRouteProps> {
render () {
const {component: Component, ...rest}: PrivateRouteProps = this.props;
const renderComponent: RenderComponent = (props) => (
AuthenticationService.isAuthenticated()
? <Component {...props} />
: <Redirect to='/login' />
);
return (
<Route {...rest} render={renderComponent} />
);
}
}
here is my solution using "react-router-dom": "^4.4.0-beta.6"
and "typescript": "3.2.2"
import React, { FunctionComponent } from "react";
import {
Route,
Redirect,
RouteProps,
RouteComponentProps
} from "react-router-dom";
interface PrivateRouteProps extends RouteProps {
component:
| React.ComponentType<RouteComponentProps<any>>
| React.ComponentType<any>;
}
const PrivateRoute: FunctionComponent<PrivateRouteProps> = ({
component: Component,
...rest
}) => {
return (
<Route
{...rest}
render={props =>
true ? ( //put your authenticate logic here
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/signin"
}}
/>
)
}
/>
);
};
export default PrivateRoute;