TL;DR: ReactJS is a UI library that's a very powerful tool amongst frontend developers in building JavaScript applications. In this article, I'll introduce you to a few features coming to ReactJS.
ReactJS is a JavaScript library, built and maintained by Facebook. As of today, it powers so many popular web and mobile platforms such as Twitter, Airbnb, Lyft, Dropbox, Pinterest, Whatsapp and Instagram. The latest release of ReactJS which is React 16 ships with a lot of features such as
Error Boundaries
, Custom DOM Attributes definition
, Fragments as return types
, Portals
and many others.However, the ReactJS team is not slacking. They are hard at work looking for new ways to make React a highly performant library in UI component development. A sneak peek into new features coming to React was demoed by the creator of Redux and React core team member, Dan Abramov at JSConf Iceland, 2018. If you haven't watched Dan's talk, here is the demo.
What's coming to ReactJS?
Making it easier for developers to build great user experiences using ReactJS has always been the goal of the ReactJS team. Building for great user experiences involves splitting the attention of developers into two distinct categories:
- Computing Power
- Network Speed
With these categories spelt out, you start asking the following questions:
- Are the users on a slow network? If so, how's the user experience? Can we (developers) control the loading states?
- Are the users on a low-end device (devices with low CPU power)? If so, is using the app still a memorable experience effectively?
- Are the users on a fast network? If so, is the experience seamless? No janky UI.
- Are the users on a high-end device (devices with high CPU power)? If so, is the rendering flawless?
These are valid questions that need answers. Let's explore how Time Slicing and Suspense in ReactJS can help deliver the best user experience for everyone.
Time Slicing
In Dan's talk, he said: "We’ve built a generic way to ensure that high-priority updates like user input don’t get blocked by rendering low-priority updates". What does this mean? The ReactJS team named this concept Time Slicing. Let me explain in simpler terms.
“Dan Abramov: We’ve built a generic way to ensure that high-priority updates like user input don’t get blocked by rendering low-priority updates.”
Tweet This
ReactJS is concerned about a device's CPU power. While rendering, ReactJS ensures that it doesn't block the thread thus causing the app to freeze.
Time-slicing allows ReactJS, which now runs on React Fiber, to split computations of updates on children components into chunks during idle callbacks and rendering work is spread out over multiple frames. Now, during the process of asynchronous rendering, it ensures that if a user's device is very fast, updates within the app feel synchronous and if a user's device is slow, the app feels responsive. No freezing, No janky UI experience!
Suspense
In Dan's talk, he said: "We have built a generic way for components to suspend rendering while they load asynchronous data".
The simple definition of the suspense feature is that ReactJS can pause any state update until the data been fetched is ready to be rendered. In essence, ReactJS suspends the component tree while waiting for the data to be fetched completely. During the suspension, it goes ahead to handle other high-priority updates.
“Dan Abramov: We have built a generic way for components to suspend rendering while they load asynchronous data.”
Tweet This
Andrew Clark, (author of the suspense feature), gave a practical breakdown of how the suspense feature works in the tweets below:
Here's how suspending works:
- in the render method, read a value from the cache.
- if the value is already cached, the render continues like normal
- if the value is not already cached, the cache throws a promise
- when the promise resolves, React retries where it left off— Andrew Clark (@acdlite) March 1, 2018
Dan used an API from
Future React
called createFetcher
(this name is likely to change) in his demo to demonstrate how the suspense feature works. The createFetcher
function is a basic cache system that allows React to suspend the data fetching request from within the render method. According to Andrew Clark, it's called the simple-cache-provider.createFetcher from @dan_abramov's talk is this thing:
We're calling it simple-cache-provider (for now). It's a basic cache that works for 80% of use cases, and (when it's done) will serve as a reference implementation for Apollo, Relay, etc.https://t.co/elI6YFco0A— Andrew Clark (@acdlite) March 1, 2018
I'll borrow a practical example from the React Apollo's team initial implementation to show you how suspense works. The React Apollo team imitated the suspense feature by suspending the result of a GraphQL query.
const MOVIE_QUERY = gql` query GetMovie($id: Int!) { movie(id: $id) { id title overview poster_path } } `; function MovieInfo({ movie, clearActiveResult }) { return ( <Query asyncMode query={MOVIE_QUERY} variables={{ id: movie.id }}> {({ data: { movie } }) => ( <Fragment> <FullPoster movie={movie} /> <h2>{movie.title}</h2> <div>{movie.overview}</div> </Fragment> )} </Query> ); }
GraphQL query
In the example above, there is an
asyncMode
prop attached to the Query component
that allows for async rendering. The sample code below shows the render method in the Query
component..... if (loading) { if(this.props.asyncMode) { throw this.state.queryObservable!.result(); } } Object.assign(data.data, this.previousData, currentResult.data);
Query component render method
If the
async
mode is turned on, rendering is suspended till data is fetched. In the example above, it throws a promise in async mode. During the suspension, a developer can now effectively control the loading states via using a component with a prop that has a time limit attached to it as shown below or through the loading
API method from the createFetcher
.<Placeholder delayMs={1000} fallback={<Loadingsize="medium" color="blue" />}
Note: Dan Abramov mentioned that there is the concept of a loading API from the
simple-cache-provider
. It might land as a Loading
component or the name might change in upcoming ReactJS releases.In React, if a component suspends, we keep rendering the siblings as deeply as we can. Suspend means "don't commit," not "don't reconcile." - Andrew Clark
Did You Know™
React Suspense doesn't necessarily rely on async rendering. It works in sync mode, too. But the downside is that those <Placeholder /> components are immediately triggered, without any delay.
Thoseprops illustrate why async rendering is so good for UX.delayMs
— Andrew Clark (@acdlite) March 1, 2018
Did You Know™
You can suspend from inside of getDerivedStateFromProps. It “just works” because it’s part of React’s render phase.
You can also suspend inside a setState reducer (first argument).— Andrew Clark (@acdlite) March 2, 2018
Another use case is
Code splitting
. Code splitting is a concept used in improving performance by loading only the code a particular page needs in an application. Code splitting
works well with the suspense
feature.import { createFetcher } from 'React'; // dynamic imports const moviePageFetcher = createFetcher( () => import('./MoviePage') ); // get the MoviePage component const moviePage = moviePageFetcher.read().default; ...
The code above loads the MoviePage component only when it is needed while taking advantage of the suspense feature via the
createFetcher
method.Aside: Securing React Apps with Auth0
As you will learn in this section, you can easily secure your React applications with Auth0, a global leader in Identity-as-a-Service (IDaaS) that provides thousands of enterprise customers with modern identity solutions. Alongside with the classic username and password authentication process, Auth0 allows you to add features like Social Login, Multifactor Authentication, Passwordless Login, and much more with just a few clicks.
To follow along the instruction describe here, you will need an Auth0 account. If you don't have one yet, now is a good time to sign up for a free Auth0 account.
Also, if you want to follow this section in a clean environment, you can easily create a new React application with just one command:
npx create-react-app react-auth0
Then, you can move into your new React app (which was created inside a new directory called
react-auth0
by the create-react-app
tool), and start working as explained in the following subsections.Setting Up an Auth0 Application
To represent your React application in your Auth0 account, you will need to create an Auth0 Application. So, head to the Applications section on your Auth0 dashboard and proceed as follows:
- click on the Create Application button;
- then define a Name to your new application (e.g., "React Demo");
- then select Single Page Web Applications as its type.
- and hit the Create button to end the process.
After creating your application, Auth0 will redirect you to its Quick Start tab. From there, you will have to click on the Settings tab to whitelist some URLs that Auth0 can call after the authentication process. This is a security measure implemented by Auth0 to avoid the leaking of sensitive data (like ID Tokens).
So, when you arrive at the Settings tab, search for the Allowed Callback URLs field and add
http://localhost:3000/callback
into it. For this tutorial, this single URL will suffice.That's it! From the Auth0 perspective, you are good to go and can start securing your React application.
Dependencies and Setup
To secure your React application with Auth0, there are only three dependencies that you will need to install:
: This is the default library to integrate web applications with Auth0.auth0.js
: This is the de-facto library when it comes to routing management in React.react-router
: This is the extension to the previous library to web applications.react-router-dom
To install these dependencies, move into your project root and issue the following command:
npm install --save auth0-js react-router react-router-dom
Note: As you want the best security available, you are going to rely on the Auth0 login page. This method consists of redirecting users to a login page hosted by Auth0 that is easily customizable right from your Auth0 dashboard. If you want to learn why this is the best approach, check the Universal vs. Embedded Login article.
After installing all three libraries, you can create a service to handle the authentication process. You can call this service
Auth
and create it in the src/Auth/
directory with the following code:// src/Auth/Auth.js import auth0 from 'auth0-js'; export default class Auth { constructor() { this.auth0 = new auth0.WebAuth({ // the following three lines MUST be updated domain: '<AUTH0_DOMAIN>', audience: 'https://<AUTH0_DOMAIN>/userinfo', clientID: '<AUTH0_CLIENT_ID>', redirectUri: 'http://localhost:3000/callback', responseType: 'token id_token', scope: 'openid profile', }); this.getProfile = this.getProfile.bind(this); this.handleAuthentication = this.handleAuthentication.bind(this); this.isAuthenticated = this.isAuthenticated.bind(this); this.login = this.login.bind(this); this.logout = this.logout.bind(this); this.setSession = this.setSession.bind(this); } getProfile() { return this.profile; } handleAuthentication() { return new Promise((resolve, reject) => { this.auth0.parseHash((err, authResult) => { if (err) return reject(err); console.log(authResult); if (!authResult || !authResult.idToken) { return reject(err); } this.setSession(authResult); resolve(); }); }); } isAuthenticated() { return new Date().getTime() < this.expiresAt; } login() { this.auth0.authorize(); } logout() { // clear id token and expiration this.idToken = null; this.expiresAt = null; } setSession(authResult) { this.idToken = authResult.idToken; this.profile = authResult.idTokenPayload; // set the time that the id token will expire at this.expiresAt = authResult.expiresIn * 1000 + new Date().getTime(); } }
The
Auth
service that you just created contains functions to deal with different steps of the sign in/sign up process. The following list briefly summarizes these functions and what they do:
: This function returns the profile of the logged-in user.getProfile
: This function looks for the result of the authentication process in the URL hash. Then, the function processes the result with thehandleAuthentication
method fromparseHash
.auth0-js
: This function checks whether the expiry time for the user's ID token has passed.isAuthenticated
: This function initiates the login process, redirecting users to the login page.login
: This function removes the user's tokens and expiry time.logout
: This function sets the user's ID token, profile, and expiry time.setSession
Besides these functions, the class contains a field called
auth0
that is initialized with values extracted from your Auth0 application. It is important to keep in mind that you have to replace the <AUTH0_DOMAIN>
and <AUTH0_CLIENT_ID>
placeholders that you are passing to the auth0
field.Note: For the
placeholders, you will have to replace them with something similar to<AUTH0_DOMAIN>
, whereyour-subdomain.auth0.com
is the subdomain you chose while creating your Auth0 account (or your Auth0 tenant). For theyour-subdomain
, you will have to replace it with the random string copied from the Client ID field of the Auth0 Application you created previously.<AUTH0_CLIENT_ID>
Since you are using the Auth0 login page, your users are taken away from the application. However, after they authenticate, users automatically return to the callback URL that you set up previously (i.e.,
http://localhost:3000/callback
). This means that you need to create a component responsible for this route.So, create a new file called
Callback.js
inside src/Callback
(i.e., you will need to create the Callback
directory) and insert the following code into it:// src/Callback/Callback.js import React from 'react'; import { withRouter } from 'react-router'; function Callback(props) { props.auth.handleAuthentication().then(() => { props.history.push('/'); }); return <div>Loading user profile.</div>; } export default withRouter(Callback);
This component, as you can see, is responsible for triggering the
handleAuthentication
process and, when the process ends, for pushing users to your home page. While this component processes the authentication result, it simply shows a message saying that it is loading the user profile.After creating the
Auth
service and the Callback
component, you can refactor your App
component to integrate everything together:// src/App.js import React from 'react'; import { withRouter } from 'react-router'; import { Route } from 'react-router-dom'; import Callback from './Callback/Callback'; import './App.css'; function HomePage(props) { const { authenticated } = props; const logout = () => { props.auth.logout(); props.history.push('/'); }; if (authenticated) { const { name } = props.auth.getProfile(); return ( <div> <h1>Howdy! Glad to see you back, {name}.</h1> <button onClick={logout}>Log out</button> </div> ); } return ( <div> <h1>I don't know you. Please, log in.</h1> <button onClick={props.auth.login}>Log in</button> </div> ); } function App(props) { const authenticated = props.auth.isAuthenticated(); return ( <div className="App"> <Route exact path="/callback" render={() => <Callback auth={props.auth} />} /> <Route exact path="/" render={() => ( <HomePage authenticated={authenticated} auth={props.auth} history={props.history} /> )} /> </div> ); } export default withRouter(App);
In this case, you are actually defining two components inside the same file (just for the sake of simplicity). You are defining a
HomePage
component that shows a message with the name of the logged-in user (that is, when the user is logged in, of course), and a message telling unauthenticated users to log in.Also, this file is making the
App
component responsible for deciding what component it must render. If the user is requesting the home page (i.e., the /
route), the HomePage
component is shown. If the user is requesting the callback page (i.e., /callback
), then the Callback
component is shown.Note that you are using the
Auth
service in all your components (App
, HomePage
, and Callback
) and also inside the Auth
service. As such, you need to have a global instance for this service, and you have to include it in your App
component.So, to create this global
Auth
instance and to wrap things up, you will need to update your index.js
file as shown here:// src/index.js import React from 'react'; import ReactDOM from 'react-dom'; import { BrowserRouter } from 'react-router-dom'; import Auth from './Auth/Auth'; import './index.css'; import App from './App'; import registerServiceWorker from './registerServiceWorker'; const auth = new Auth(); ReactDOM.render( <BrowserRouter> <App auth={auth} /> </BrowserRouter>, document.getElementById('root'), ); registerServiceWorker();
After that, you are done! You just finished securing your React application with Auth0. If you take your app for a spin now (
npm start
), you will be able to authenticate yourself with the help of Auth0, and you will be able to see your React app show your name (that is, if your identity provider does provide a name).If you are interested in learning more, please, refer to the official React Quick Start guide to see, step by step, how to properly secure a React application. Besides the steps shown in this section, the guide also shows:
Conclusion
I'm overwhelmed by the engagement of React core team members with members of the JavaScript community in landing new features in React. Worthy of note is that the APIs mentioned in this article are experimental. This is an introduction to the features that are expected to land in React soon. I'm anxious. I can't wait for these APIs to be stable.
I'll be sure to cover these features in detail and any improvements made to them when they finally land in ReactJS.
What are your thoughts on these features? Let me know in the comments section! 😊
About the author
Prosper Otemuyiwa
Former Auth0 Employee