A simple solution would be to make a HOC
(High Order Component) that wraps all protected routes.
Depending upon how nested your app is, you may want to utilize local state or redux
state.
Working example: https://codesandbox.io/s/5m2690nn6n (this uses local state)
routes/index.js
import React from "react";
import { BrowserRouter, Switch, Route } from "react-router-dom";
import Home from "../components/Home";
import Players from "../components/Players";
import Schedule from "../components/Schedule";
import RequireAuth from "../components/RequireAuth";
export default () => (
<BrowserRouter>
<RequireAuth>
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/players" component={Players} />
<Route path="/schedule" component={Schedule} />
</Switch>
</RequireAuth>
</BrowserRouter>
);
components/RequireAuth.js
import React, { Component, Fragment } from "react";
import { withRouter } from "react-router-dom";
import Login from "./Login";
import Header from "./Header";
class RequireAuth extends Component {
state = { isAuthenticated: false };
componentDidMount = () => {
if (!this.state.isAuthenticated) {
this.props.history.push("/");
}
};
componentDidUpdate = (prevProps, prevState) => {
if (
this.props.location.pathname !== prevProps.location.pathname &&
!this.state.isAuthenticated
) {
this.props.history.push("/");
}
};
isAuthed = () => this.setState({ isAuthenticated: true });
unAuth = () => this.setState({ isAuthenticated: false });
render = () =>
!this.state.isAuthenticated ? (
<Login isAuthed={this.isAuthed} />
) : (
<Fragment>
<Header unAuth={this.unAuth} />
{this.props.children}
</Fragment>
);
}
export default withRouter(RequireAuth);
Or, instead of wrapping routes, you can create a protected component that houses protected routes.
Working example: https://codesandbox.io/s/yqo75n896x (uses redux
instead of local state).
routes/index.js
import React from "react";
import { BrowserRouter, Route, Switch } from "react-router-dom";
import { createStore } from "redux";
import { Provider } from "react-redux";
import Home from "../components/Home";
import Header from "../containers/Header";
import Info from "../components/Info";
import Sponsors from "../components/Sponsors";
import Signin from "../containers/Signin";
import RequireAuth from "../containers/RequireAuth";
import rootReducer from "../reducers";
const store = createStore(rootReducer);
export default () => (
<Provider store={store}>
<BrowserRouter>
<div>
<Header />
<Switch>
<Route exact path="/" component={Home} />
<Route path="/info" component={Info} />
<Route path="/sponsors" component={Sponsors} />
<Route path="/protected" component={RequireAuth} />
<Route path="/signin" component={Signin} />
</Switch>
</div>
</BrowserRouter>
</Provider>
);
containers/RequireAuth.js
import React from "react";
import { Route, Redirect } from "react-router-dom";
import { connect } from "react-redux";
import ShowPlayerRoster from "../components/ShowPlayerRoster";
import ShowPlayerStats from "../components/ShowPlayerStats";
import Schedule from "../components/Schedule";
const RequireAuth = ({ match: { path }, isAuthenticated }) =>
!isAuthenticated ? (
<Redirect to="/signin" />
) : (
<div>
<Route exact path={`${path}/roster`} component={ShowPlayerRoster} />
<Route path={`${path}/roster/:id`} component={ShowPlayerStats} />
<Route path={`${path}/schedule`} component={Schedule} />
</div>
);
export default connect(state => ({
isAuthenticated: state.auth.isAuthenticated
}))(RequireAuth);
You can even get more modular by creating a wrapper function. You would be able to pick and choose any route by simply wrapping over the component. I don't have a codebox example, but it would be similar to this setup.
For example: <Route path="/blog" component={RequireAuth(Blog)} />