Friday, November 22

Build Your First App with React's Context API

One of React’s latest features is the Context API. Up until now, Context within React has been somewhat experimental, but still used in quite a few popular libraries, like Redux and React Router. There are plenty of ways to avoid using Context, but sometimes it can be really useful.

One really good use case for Context is keeping track of authentication. In fact, React’s official documentation for the Context API mentions it can be used for authentication.

Context is designed to share data that can be considered “global” for a tree of React components, such as the current authenticated user, theme, or preferred language.

The examples in their documentation are helpful, but they don’t actually include an example of sharing the authenticated user. I’ll show you here how one way to use the new Context API to simply keep track of whether a user is authenticated, and some basic profile information.

React Context in a Nutshell

Before going too far down the rabbit hole, let me give you a quick introduction into the Context API. As of React version 16, React ships with a function called createContext that takes a single value parameter. Calling createContext gives you both a Provider and Consumer component.

You can render the Provider component anywhere in your app – in multiple places if you like. If there is a Consumer anywhere inside the Provider (even deeply nested somewhere), it will have access to that value parameter in the form of a render function as a child. Each Provider can also override the value. For example:

import { createContext } from 'react';

const { Provider, Consumer } = createContext('default value');

export default () =>
  <main>
    <Provider>
      <Consumer>{value => <span>{value}</span>}</Consumer>
    </Provider>
    <Provider value="overridden value">
      <div>
        <Consumer>{value => <span>{value}</span>}</Consumer>
      </div>
    </Provider>
  </main>
);

Will render the following:

<main>
  <span>default value</span>
  </div>
    <span>overridden value</span>
  </div>
</main>

Because Provider is just a component, you can easily create a wrapper component that passes its state into the value prop of the Provider. Then you will have access to that component’s state anywhere in the app via the Consumer. In this tutorial, I’ll show you how to use one component to keep track of auth state changes and pass that information down via the Context API.

Build a Simple React App

The quickest way to get started with React in my experience is using the official Create React App CLI. It bootstraps an app for you giving you some decent defaults. To use it, install it globally with NPM, then run it with the name of your app as a parameter. This will create a new folder with everything you need to get started, and will even install the dependencies for you. You can then run the app using yarn start.

npm install --global create-react-app@1.5.2 yarn@1.7.0
create-react-app react-context
cd react-context
yarn start

You should now be able to see your app running by visiting http://localhost:3000 with your browser.

For this tutorial, I’ll show you how you can take advantage of the new Context API to cut down some of the boilerplate that is often paired with authentication. Before getting started with Context, you’ll need to set up authentication in order to tie it in.

Create an Okta Application

One simple way to add authentication to your project is with Okta. Okta is a cloud service that allows developers to create, edit, and securely store user accounts and user account data, and connect them with one or multiple applications. Our API enables you to:

If you don’t already have one, sign up for a forever-free developer account. Log in to your developer console, navigate to Applications, then click Add Application. Select SinglePage App, then click Next.

Since Create React App runs on port 3000 by default, you should add that as a Base URI and Login Redirect URI. Your settings should look like the following:

OIDC Application Settings
Click Done to save your app, then copy your Client ID and paste it as a variable into a file called .env.local in the root of your project. This will allow you to access the file in your code without needing to store credentials in source control. You’ll also need to add your organization URL (without the -admin suffix). Environment variables (other than NODE_ENV) need to start with REACT_APP_ in order for Create React App to read them, so the file should end up looking like this:

.env.local

REACT_APP_OKTA_CLIENT_ID={yourClientId}
REACT_APP_OKTA_ORG_URL=https://{yourOktaDomain}

The easiest way to add authentication with Okta is to use Okta’s React SDK. You’ll also need to add routes, which can be done using React Router. I’ll also have you start adding icons to the app (for now as an avatar icon to show you’re logged in).

yarn add @okta/okta-react@1.0.3 react-router-dom@4.3.1

For routes to work properly using React Router, you need to wrap your whole application in a router (for the web, you need to use BrowserRouter). Similarly, to allow access to authentication anywhere in the app, you need to wrap the app in a Security component provided by Okta. Okta also needs access to the router, so the Security component should be nested inside the router. You should modify your src/index.js file to look like the following:

src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import { Security } from '@okta/okta-react';

import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';

const oktaConfig = {
  issuer: `${process.env.REACT_APP_OKTA_ORG_URL}/oauth2/default`,
  redirect_uri: `${window.location.origin}/implicit/callback`,
  client_id: process.env.REACT_APP_OKTA_CLIENT_ID,
};

ReactDOM.render(
  <BrowserRouter>
    <Security {...oktaConfig}>
      <App />
    </Security>
  </BrowserRouter>,
  document.getElementById('root'),
);

registerServiceWorker();

Now in src/App.js you can use Routes that tell the app to only render a certain component if the current URL matches the given path. You can also use Links to create a link to that route (or page) and properly modify the browser’s history to work in a single-page app.

Go ahead and create a nav tag with links to the Home page (/) and a Profile page (/profile). Also, create a main tag containing your routes. For now just stub in some inline components that render the name of the page.

By using Okta’s SecureRoute you will find that whenever you try to go to that page you will need to be logged in. If you’re not currently logged in, you’ll be redirected to /implicit/callback to log in. In order for that route to actually log you in, you also need to create a route that renders Okta’s ImplicitCallback component.

src/App.js

--- a/src/App.js
+++ b/src/App.js
@@ -1,4 +1,7 @@
 import React, { Component } from 'react';
+import { Link, Route } from 'react-router-dom';
+import { SecureRoute, ImplicitCallback } from '@okta/okta-react';
+
 import logo from './logo.svg';
 import './App.css';

@@ -9,10 +12,16 @@ class App extends Component {
         <header className="App-header">
           <img src={logo} className="App-logo" alt="logo" />
           <h1 className="App-title">Welcome to React</h1>
+          <nav className="App-nav">
+            <Link to="/">Home</Link>
+            <Link to="/profile">Profile</Link>
+          </nav>
         </header>
-        <p className="App-intro">
-          To get started, edit <code>src/App.js</code> and save to reload.
-        </p>
+        <main className="App-intro">
+          <Route exact path="/" component={() => 'Home Page'} />
+          <SecureRoute exact path="/profile" component={() => 'Profile page'} />
+          <Route path="/implicit/callback" component={ImplicitCallback} />
+        </main>
       </div>
     );
   }

Note: You can apply a diff in git by copying it to a file and using the git apply command. If you’re following along this avoids having to write the file out manually. For example, if you save the above as a file named app.diff, then from your project folder, you could type git apply app.diff and it should apply those changes.

To make this look a little nicer, add a bit of CSS to let the header expand to the content, give the main part of the app some padding, and make the nav links a bit more readable.

src/App.css

--- a/src/App.css
+++ b/src/App.css
@@ -9,17 +9,35 @@

 .App-header {
   background-color: #222;
-  height: 150px;
+  min-height: 150px;
   padding: 20px;
   color: white;
 }

+.App-nav {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.App-nav a {
+  color: white;
+  padding: 0 5px;
+  text-decoration: none;
+  cursor: pointer;
+}
+
+.App-nav a:hover {
+  text-decoration: underline;
+}
+
 .App-title {
   font-size: 1.5em;
 }

 .App-intro {
   font-size: large;
+  margin: 1em;
 }

You should now have a slightly modified version of the default page from Create React App that says “Home page”. You should also now be able to click on the Profile link and you will be prompted to log in to your account. Upon success, you’ll be taken to the profile page, that at the moment just says Profile page.

Note: You may need to restart the development server after adding the environment variables. You can do this by going to the terminal running the server and hitting ctrlc, then re-running the yarn start command.

Home page

Add a User Greeting with React Context

Using React’s new Context API, you can create a component that you will use to wrap your entire application, similar to the way BrowserRouter or Security works. Once that Provider is in place, you can access pieces of authentication anywhere in the application without a lot of boilerplate.

In this case, create a context using createContext. You’ll then check when the component is first mounted, and again whenever it updates, if the user is authenticated. If this is different from the current state, then it also checks for the user and saves that as well. The reason the state is only updated whenever the authentication is different from the previous state is that otherwise, the componentDidUpdate function would trigger another update, sending this component into an infinite render loop.

To keep this simple to use, the default export is the Context’s Consumer. The Provider component is exported separately and is wrapped with Okta’s withAuth to give it access to the auth prop.

Note: Checking authentication during componentDidUpdate is admittedly not the most efficient method. At the time of this writing, there is not a simple way to detect when the state of authorization changes, so this is a simple approach for demo purposes. This example might not work well for larger applications.

src/Auth.js

import React, { createContext, Component } from 'react';
import { withAuth } from '@okta/okta-react';

const AuthContext = createContext({
  user: null,
  isAuthenticated: null,
});

export default AuthContext.Consumer;

class AuthController extends Component {
  state = {
    user: null,
    isAuthenticated: null,
  }

  componentDidUpdate() {
    this.checkAuthentication();
  }

  componentDidMount() {
    this.checkAuthentication();
  }

  async checkAuthentication() {
    const isAuthenticated = await this.props.auth.isAuthenticated();
    if (isAuthenticated !== this.state.isAuthenticated) {
      const user = await this.props.auth.getUser();
      this.setState({ isAuthenticated, user });
    }
  }

  render() {
    return (
      <AuthContext.Provider value={this.state}>
        {this.props.children}
      </AuthContext.Provider>
    );
  }
}

export const AuthProvider = withAuth(AuthController);

The app’s main entry point now needs to be wrapped in the AuthProvider. It needs to be inside Security so that it can access the auth using withAuth.

src/index.js

--- a/src/index.js
+++ b/src/index.js
@@ -6,6 +6,7 @@ import { Security } from '@okta/okta-react';
 import './index.css';
 import App from './App';
 import registerServiceWorker from './registerServiceWorker';
+import { AuthProvider } from './Auth';

 const oktaConfig = {
   issuer: `${process.env.REACT_APP_OKTA_ORG_URL}/oauth2/default`,
@@ -16,7 +17,9 @@ const oktaConfig = {
 ReactDOM.render(
   <BrowserRouter>
     <Security {...oktaConfig}>
-      <App />
+      <AuthProvider>
+        <App />
+      </AuthProvider>
     </Security>
   </BrowserRouter>,
   document.getElementById('root')

Now that the Context Provider is set up and ready to go, you can start using the Consumer. For a quick example, try changing the homepage to greet the user when they’re logged in. If they aren’t logged in, keep the old message of Welcome to React.

React Contexts take a render function as its child element. The parameters passed in are going to be the state of the provider (the data passed into value). Because of this, to access the user, you need to pass in a function that expects an object containing the key user. If this is null then authentication is still loading. If it’s undefined then the user is not authenticated. Otherwise, it will contain the user’s basic profile information. Go ahead and ignore the loading state for now, and only greet the user once you have their information.

src/App.js
``diff
— a/src/App.js
+++ b/src/App.js
@@ -5,13 +5,21 @@ import { SecureRoute, ImplicitCallback } from ‘@okta/okta-react’;
import logo from ‘./logo.svg’;
import ‘./App.css’;

+import Auth from ‘./Auth’;
+
+const welcomeUser = ({ user }) => user

  • ? Welcome, ${user.given_name || user.name}!
  • : ‘Welcome to React’;
  • class App extends Component {
    render() {
    return (

    logo

  • Welcome to React

  • {welcomeUser}
  • Logged in greeting

    Create a Login/Logout Link

    A pretty common scenario with authentication is having a button or link that you can click to log you in when you’re authenticated. You can use the same React Context to determine whether or not a user is logged in.

    In order to properly log the user in or out when they click the link, you can wrap the component with withAuth to get access to the login and logout functions of auth.

    This time, you don’t want the user to click either Login or Logout if you’re not sure what the state of authentication is yet. You could implement some sort of loading icon here, but for this demo just render nothing until you know whether or not the user is logged in.

    src/LoginButton.js

    import React from 'react';
    import { withAuth } from '@okta/okta-react';
    
    import Auth from './Auth';
    
    export default withAuth(({ auth }) => (
      <Auth>
        {({ isAuthenticated }) => {
          if (isAuthenticated === null) return null;
    
          return (
            <a onClick={() => isAuthenticated ? auth.logout() : auth.login()}>
              {isAuthenticated ? 'Logout' : 'Login'}
            </a>
          );
        }}
      </Auth>
    ));

    Now you can add the Login/Logout link to your list of nav items:

    src/App.js

    --- a/src/App.js
    +++ b/src/App.js
    @@ -6,6 +6,7 @@ import logo from './logo.svg';
     import './App.css';
    
     import Auth from './Auth';
    +import LoginButton from './LoginButton';
    
     const welcomeUser = ({ user }) => user
       ? `Welcome, ${user.given_name || user.name}!`
    @@ -23,6 +24,7 @@ class App extends Component {
               <nav className="App-nav">
                 <Link to="/">Home</Link>
                 <Link to="/profile">Profile</Link>
    +            <LoginButton />
               </nav>
             </header>
             <main className="App-intro">

    Homepage with Login button

    Homepage with Logout button

    Create a Real Profile Page

    From the user value in your Context, you can also now get access to the user’s basic profile information.

    Semantically, a dl HTML tag is a “Description List” element. This is a good, basic way to display metadata. However, you’ll probably want to add a little bit of simple styling. This will make sure the text isn’t center-aligned, and make the term (the dt tag) bold. Create a new file Profile.css that you’ll import in your profile page.

    src/Profile.css

    dl {
      text-align: left;
    }
    
    dt {
      padding: 4px 0;
      font-weight: bold;
    }

    Lodash provides a suite of utilities to manipulate data. To keep things simple and automated, try using that to convert the name of a key to something more personable. The startCase function will convert a string like given_name to Given Name. To add Lodash as a dependency, run the following:

    yarn add lodash@4.17.10

    The updated_at key is actually just a number representing the number of seconds from 1970, so you can convert that to a human-readable string in JavaScript using new Date(updated_at * 1000).toString().

    Putting all that together, you can create a functional component that takes a user prop, and wrap that in the Auth Context Consumer. This time, show a simple loading message if the user isn’t loaded yet. Technically, the user will be undefined if authentication finished and there is no user, but because this is in a SecureRoute, if there is no user then the page will redirect to the home page.

    src/Profile.js

    import React, { Fragment } from 'react';
    import { startCase } from 'lodash';
    
    import Auth from './Auth';
    import './Profile.css';
    
    const ProfilePage = ({ user }) => {
      if (!user) return 'Loading...';
    
      return (
        <dl>
          {Object.keys(user).sort().map(key => (
            <Fragment key={key}>
              <dt>{startCase(key)}</dt>
              <dd>{key === 'updated_at' ? new Date(user[key] * 1000).toString() : user[key]}</dd>
            </Fragment>
          ))}
        </dl>
      );
    }
    
    export default () => <Auth>{ProfilePage}</Auth>;

    In your app’s routes, replace the mock profile page with a reference to the real one you just created:

    ``diff
    — a/src/App.js
    +++ b/src/App.js
    @@ -7,6 +7,7 @@ import ‘./App.css’;

    import Auth from ‘./Auth’;
    import LoginButton from ‘./LoginButton’;
    +import ProfilePage from ‘./Profile’;

    const welcomeUser = ({ user }) => user
    ? Welcome, ${user.given_name || user.name}!
    @@ -29,7 +30,7 @@ class App extends Component {

    ‘Home Page’} />
    – ‘Profile page’} />
    +

       </div>
    
    
    Now when you go to the profile page, you get some details about the logged in user.
    
    ![complete-profile-page.png](https://res.cloudinary.com/scotch/image/upload/v1535753116/qd7zqx6jjjr2zjyenxcw.png)
    
    ## Learn More About React and Authentication with Okta
    
    I hope I've given you a better understanding of React's new Context API and how it can be useful for cutting down on boilerplate or sharing simple pieces of application state. For more information and example on the Context API, check out [React's documentation](https://reactjs.org/docs/context.html). For more examples using React with Okta, check out some of these posts or browse the [Okta Developer Blog](https://developer.okta.com/blog/).
    
    * [Build a Health Tracking App with React, GraphQL, and User Authentication](https://developer.okta.com/blog/2018/07/11/build-react-graphql-api-user-authentication)
    * [Tutorial: Build a Secure CRUD App with Symfony and React](https://developer.okta.com/blog/2018/08/23/symfony-react-php-crud-app)
    * [Add Okta authentication to your React App](https://developer.okta.com/code/react/)
    * [Build User Registration with Node, React, and Okta](https://developer.okta.com/blog/2018/02/06/build-user-registration-with-node-react-and-okta)
    
    And as always, we’d love to hear from you. Hit us up with questions or feedback in the comments, or on Twitter [@oktadev](https://twitter.com/oktadev).


    Source: Scotch.io

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x