Among the changes in React 16.3 is a new stable version of the Context API. We’re going to take a look at how it works by building a protected route component.
What is Context?
Context is about encapsulating state. It allows us to pass data from a parent provider component to any subscribed component down the tree. Without state management we often have to “drill” props through every component along the way.
Isn’t that what Redux is for?
Yes, Context operates similarly to how components can connect to Redux’s global state. However, a native element like Context will often be a better solution for small to medium apps that don’t need the complex overhead of Redux.
Basic Concepts
There are three elements to Context:
createContext
— Calling this returns a pair of components,Provider
andConsumer
.Provider
— a Component that allows for one or moreConsumers
to subscribe to changes.Consumer
—a Component subscribed to a Provider
Let’s Start Building
We’re going to build an app with two routes. One is a landing page with global access. The other is a dashboard page with restricted access for logged in users. You can find the final version here.
Try it out: go to /dashboard while logged out. Log in and navigate freely between routes. From the dashboard, log out and it’ll kick you out to the landing page.
Context Header
To demonstrate Context’s basic functionality, let’s start by building a header component that lets us log in and out. First, create our context in a new file.
/* AuthContext.js */import React from 'react';const AuthContext = React.createContext();
Export a component AuthProvider
to define our state (whether the user is logged in) and pass its state to the value
prop on the Provider
. We’ll simply expose AuthConsumer
with a meaningful name.
/* AuthContext.js */...class AuthProvider extends React.Component {
state = { isAuth: false } render() {
return (
<AuthContext.Provider
value={{ isAuth: this.state.isAuth }}
>
{this.props.children}
</AuthContext.Provider>
)
}
}const AuthConsumer = AuthContext.Consumerexport { AuthProvider, AuthConsumer }
In index.js, wrap our app in AuthProvider
.
/* index.js */
import React from 'react';
import { render } from 'react-dom';
import { AuthProvider } from './AuthContext';
import Header from './Header';const App = () => (
<div>
<AuthProvider>
<Header />
</AuthProvider>
</div>
);render(<App />, document.getElementById('root'));
Now create our Header
and import our AuthConsumer
(I’m leaving styling out for clarity).
/* Header.js */
import React from 'react'
import { AuthConsumer } from './AuthContext'
import { Link } from 'react-router-dom'export default () => (
<header>
<AuthConsumer>
</AuthConsumer>
</header>
)
Context Consumers must have a function as their direct child. This will be passed the value from our Provider
.
/* Header.js */
...
export default () => (
<header>
<AuthConsumer> {({ isAuth }) => (
<div>
<h3>
<Link to="/">
HOME
</Link>
</h3> {isAuth ? (
<ul>
<Link to="/dashboard">
Dashboard
</Link>
<button>
logout
</button>
</ul>
) : (
<button>login</button>
)}
</div>
)} </AuthConsumer>
</header>
)
Because isAuth
is set to false, only the login button will be visible. Try changing the value to true
(it’ll switch to the logout button).
Ok, let’s try switching isAuth
in code. We’ll pass a login and logout function from our Provider
.
/* AuthContext.js */
...
class AuthProvider extends React.Component {
state = { isAuth: false } constructor() {
super()
this.login = this.login.bind(this)
this.logout = this.logout.bind(this)
} login() {
// setting timeout to mimic an async login
setTimeout(() => this.setState({ isAuth: true }), 1000)
} logout() {
this.setState({ isAuth: false })
} render() {
return (
<AuthContext.Provider
value={{
isAuth: this.state.isAuth,
login: this.login,
logout: this.logout
}}
>
{this.props.children}
</AuthContext.Provider>
)
}
}
These functions will allow us to toggle our auth state in Header
.
/* Header.js */
...
export default () => (
<header>
<AuthConsumer>
{({ isAuth, login, logout }) => (
<div>
<h3>
<Link to="/">
HOME
</Link>
</h3> {isAuth ? (
<ul>
<Link to="/dashboard">
Dashboard
</Link>
<button onClick={logout}>
logout
</button>
</ul>
) : (
<button onClick={login}>login</button>
)}
</div>
)}
</AuthConsumer>
</header>
)
Protected Route With Context
Now that we have covered the basics, let’s extend what we’ve learned to create a protected route component.
First make Landing
and Dashboard
page components. Our dashboard will only be visible when the user is logged in. Both pages will be as simple, as below:
/* Dashboard.js */
import React from 'react'const Dashboard = () => <h2>User Dashboard</h2>export default Dashboard
Now let’s route to these pages.
/* index.js */
import React from 'react';
import { render } from 'react-dom';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import { AuthProvider } from './AuthContext';
import Landing from './Landing';
import Dashboard from './Dashboard';
import Header from './Header';const App = () => (
<div>
<Router>
<AuthProvider>
<Header />
<Switch>
<Route path="/dashboard" component={Dashboard} />
<Route path="/" component={Landing} />
</Switch>
</AuthProvider>
</Router>
</div>
);render(<App />, document.getElementById('root'));
In this current state you can navigate to both /
and /dashboard
. We’ll make a special route component that checks if a user is logged in called ProtectedRoute
. The set up is similar to our Header
component.
/* ProtectedRoute.js */
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import { AuthConsumer } from './AuthContext';const ProtectedRoute = () => (
<AuthConsumer>
{({ isAuth }) => ( )}
</AuthConsumer>
);export default ProtectedRoute;
The private route will function just like a regular react-router
route, so we’ll expose the component and any other props passed to it.
const ProtectedRoute = ({ component: Component, ...rest }) => (
Now the interesting part: we’ll use the isAuth
variable to determine if it should redirect or render the protected route’s component.
const ProtectedRoute = ({ component: Component, ...rest }) => (
<AuthConsumer>
{({ isAuth }) => (
<Route
render={
props =>
isAuth
? <Component {...props} />
: <Redirect to="/" />
}
{...rest}
/>
)}
</AuthConsumer>
)
In our index
file let’s import ProtectedRoute
and use it on our dashboard route.
/* index.js */
... <ProtectedRoute path="/dashboard" component={Dashboard} />
Awesome, now we have protected routes! Try pointing the browser to /dashboard
and watch it kick you back to the landing page.
Again, here’s the link for the working demo. Read more about Context from React’s Official Docs.
Source : https://medium.com/free-code-camp/how-to-protect-your-routes-with-react-context-717670c4713a
No comments:
Post a Comment