-
-
Save ryanflorence/eba97731b5579a1c01702c9d394b3feb to your computer and use it in GitHub Desktop.
let UserContext = React.createContext(); | |
class App extends React.Component { | |
state = { | |
user: null, | |
setUser: user => { | |
this.setState({ user }); | |
} | |
}; | |
render() { | |
return ( | |
<UserContext.Provider value={this.state}> | |
<Router> | |
<Home path="/" /> | |
<About path="/about" /> | |
<PrivateRoute as={Dashboard} path="/dashboard" /> | |
</Router> | |
</UserContext.Provider> | |
); | |
} | |
} | |
class PrivateRoute extends React.Component { | |
static contextType = UserContext; | |
render() { | |
let { as: Comp, ...props } = this.props; | |
return this.context.user ? <Comp {...props} /> : <Login />; | |
} | |
} | |
class Login extends React.Component { | |
static contextType = UserContext; | |
render() { | |
return ( | |
<form | |
onSubmit={async () => { | |
let user = await doWhateverYouNeedToDoToLogin(); | |
this.context.setUser(user); | |
}} | |
/> | |
); | |
} | |
} | |
function Home() { | |
return <div>home</div>; | |
} | |
function About() { | |
return <div>about</div>; | |
} | |
function Dashboard() { | |
return <div>Protected dashboard</div>; | |
} |
Hi
class PrivateRoute extends React.Component {
static contextType = UserContext;
render() {
let { as: Comp, ...props } = this.props;
return this.context.user ? <Comp {...props} /> : <Login />;
}
}
to me, returning <Login />
doesn't change the URL on the browser, unfortunately the <Redirect />
of the @reach/router
serves different purpose from the react-router
's
please guide
thanks
Instead of writing a custom component, I prefer to wrap the ones requiring authentication with a HOC.
This has been tested with purely functional components and hooks.
I'm using a store with MobX but this can be easily rewritten using just props.
I decided to use props.navigate instead of <Redirect />
because I can put the original url in location.state.from.
Upon a successful login, I can redirect the user back to the requested path.
const requiresAuthentication = (WrappedComponent) => {
return observer(
(props) => {
const store = useContext(StoreContext);
useEffect( () => {
if (!store.isAuthenticated) {
//console.log('not authenticated');
props.navigate('/login', { state: { from: props.uri } });
}
});
return (
<React.Fragment>
{ store.isAuthenticated &&
<WrappedComponent {...props} />
}
</React.Fragment>
);
}
);
}
export default requiresAuthentication;
Usage (remove the observer wrapper if you're not using MobX):
const Products = requiresAuthentication( observer( (props) => { ... my functional component }) );
@DevanB - I think this is what you were curious about.
// AuthContext
export const AuthContext = React.createContext();
export const AuthProvider = AuthContext.Provider;
export const AuthConsumer = AuthContext.Consumer;
// App.js
const App = () => {
const [user, setUser] = useState(null);
const updateUser = newUser => {
setUser(newUser);
}
return (
<AuthProvider
value={{
user,
updateUser
}}
>
<Router>
<Home path="/" />
<About path="/about" />
<PrivateRoute as={Dashboard} path="/dashboard" />
</Router>
</AuthProvider>
);
}
// PrivateRoute.js
const PrivateRoute = props => {
const { user } = useContext(AuthContext);
let { as: Comp, ...props } = props;
return user ? <Comp {...props} /> : <Login />;
}
// Login.js
const Login = () => {
const { user, updateUser } = useContext(AuthContext);
return (
<form
onSubmit={async () => {
let newUser = await doWhateverYouNeedToDoToLogin();
updateUser(newUser);
}}
/>
);
}
// Home.js
const Home = () => {
return <div>home</div>;
}
// About.js
const About = () => {
return <div>about</div>;
}
// Dashboard.js
const Dashboard = () => {
return <div>Protected Dashboard</div>;
}
I am having issues with implementing this with nested routes. Can anyone guide me how should i go for nested private routes ? I tried nesting inside the component with router but it just doesn't render the component at all.
For nested private routes you can pass the children:
class PrivateRoute extends React.Component {
static contextType = UserContext;
render() {
let { as: Comp, children, ...props } = this.props;
return this.context.user ? <Comp {...props} >{children}</Comp> : <Login />;
}
}
+1 for Hooks, but would also love to see how this would work with purely functional components!
const Dashboard = () => <div>dashboard</div>;
const ProtectedRoute = ({ user }) => {
return user != null ? (
<Router>
<Dashboard path="/"></Dashboard>
</Router>
) : (
<Login />
);
};
return (
<AuthProvider
value={{
user,
updateUser
}}
>
<Router>
<ProtectedRoute path="/" user={user} />
</Router>
</AuthProvider>
);
}
this is how I implemented PrivateRoute
const PrivateRoute = props => {
const { user } = useUserContext();
let { as: Comp, ...otherProps } = props;
return user ? (
<Comp {...otherProps} />
) : (
<Redirect to="/login" replace={true} noThrow={true} />
);
};
Make sure noThrow
props is set to true
otherwise you'll get exception when redirecting in development.
@christensen143 I like your hooks example but think
// PrivateRoute.js const PrivateRoute = props => { const { user } = useContext(AuthContext); let { as: Comp, ...props } = props; return user ? <Comp {...props} /> : <Login />; }
Should be
// PrivateRoute.js
const PrivateRoute = props => {
const { user } = useContext(AuthContext);
let { as: Comp, ...restOfTheProps } = props;
return user ? <Comp {...restOfTheProps } /> : <Login />;
}
to avoid an Identifier 'props' has already been declared
error.
Make sure noThrow props is set to true otherwise you'll get exception when redirecting in development.
@cvrajeesh you fixed my problem. thank you!
For the people that show up here for how to do this with nested routing, I solved it using nested routers:
// App.js
<Router>
<Login path="/login" />
<ForgotPass path="/forgot-password" />
<PrivateRoute as={Dashboard} path="/dashboard/*" />
</Router>
// Dashboard
export default ({children}) => (
<Router>
<DashHome path="/" />
<DashStatistics path="/statistics" />
</Router>
)
New to this. My <Router>
moans that I don't have path
as a prop in the children components I pass. What happens when you have dynamically created pages (like blog posts) in your gatsby-node.js
? Can't I just have...
<Router>
{authenticated && <PrivateRoute/>}
{!authenticated && <PublicRoute/>}
</Router>
...and then not have to think about it further down the component tree? Rather than pepper my whole application with <Component path="path/to/something"/>
?
Also, does <Router/>
effectively stop Gatsby from serving static files?
It won't work if we try to nest some component inside of protected component:
is there a way to get this code works?