A Quick Dive into React´s New Context API

Illustrator: Finnur Alfred Finnsen

Today we have many different front end frameworks to choose from and in that sense, one could say that we are lucky. However, we have to be careful on what we choose as it is an important choice to pick the right tool, as every project requires a framework that is the most suited for it.

For example, a previous project I was working on needed to be improved. That project was built on a very old system that was running a technology from 2005. It was built with JSP and Struts technologies. We were required to add new features which used the latest tools.

The most important requirement was not to create a single page application but rather, small “components” that communicate and show different data based on different user interactions. Our objective did not require a full front end framework with routing, views, controllers, services and so on.

With this in mind, we carefully looked through our options and chose React by Facebook. This framework would only focus on the view aspect of the system and not a complete framework that lets you build single page applications, like Angular.js by Google.

What was required for the project was many simple components that communicate and exchange data between each other, quickly and efficiently, which Redux allowed. This particular solution did not require a very advanced single web application, which was good, as our focus was to simplify things. We studied the new context API and started implementing a simple working demo that we ended up using because of it´s simplicity and ease of use.

We care deeply about TDD and wanted to design everything with TDD in mind for each React component as well as the JavaScript ES6 classes that we ended up creating. Enzyme by Airbnb was used to test our React components. That said, the testing aspect will not be covered in this article. What we will discuss here, is how we simplified the architecture and created different isolated React components that communicate with each other through React´s new Context API, instead of Redux.

Source files and live demo

Github: https://github.com/Millad/AQuickDiveIntoReactApiContext

Live demo: https://codesandbox.io/s/github/Milla…

Create a project

The best way to start a React.js project is with the official react starter command line tool to generate a new React project.

The project will have a HTML page with a root HTML element that react will use to place it´s component when the HTML document is ready loaded.

It may look like this:

<div id=“nameSearchApplicationRoot”></div>

We need two more components that live alone outside of nameSearchApplicationRoot component. These are presented below.

<div id=“personNameFoundFieldWrapper”></div>
<div id=“personSearchInputFieldWrapper”></div>

We will use different elements located in different areas to talk to each other using the new Context API by sending messages or updates to components without them being connected via a root component with it´s children.

It is really great to use JSX with React and ES6. Make sure Webpack and everything is setup so you can transpile JSX files and enjoy using JSX with React!

We start by creating the personSearchInputFieldWrapper (PersonSearch Component) and the personNameFoundFieldWrapper (PersonName component). Component for personSearchInputFieldWrapper is simply an input field that will allow the user to input their name.The second component for personNameFoundFieldWrapper will simply show the name of the person as they type. These two components are isolated from each other.

Our first class will be our init file that will be loaded upon html ready. It will create React components and inject them into these HTML Div elements we created earlier in this article, which include personSearchInputFieldWrapper, personNameFoundFieldWrapper and nameSearchApplicationRoot.

We use jQuery in our main html page to check for page ready before running our React application.

<code>
$(function () {
// App init Script
});
</code>

The usual way to start a React application is to have one div and run ReactDOM.render() to mount your React component into the HTML div in your HTML page. That component will then have children that share data up and down through the family tree. We can do the same but with Portals.  Portals can be used to mount isolated components to multiple HTML Divs that have no relationships with the main component. We use ReactDOM.render() which will combine those isolated components into a shared sandbox together with the main component. Context API will also change the way we build things. It will allow us to share a global state between isolated components created by Portals and combined by our final Render method.

The Code

This is how our app code look like to mount different components into a Sandbox that share data.

We have created a Middleware that will allow us to share state between these isolated components that live in our sandbox. The middleware have a consumer and a provider. These will be used to send and receive data between components. The middleware is a simple shared state component class that is used in the sandbox.  

Please note the code also include comments to clarify things in more details.

index.jsx:

import ReactDOM from "react-dom";
import React from "react";
import PersonName from "./PersonName";
import PersonSearch from "./PersonSearch";
import Middleware from "./Middleware";
import { Consumer, Provider } from "./Context";
import $ from "jquery";

// We use jQuery to make sure that our script runs only after everything have been loaded by the browser.
$(function() {
 // We get the elements from our HTML page
 const personNameField = document.getElementById(
   "personNameFoundFieldWrapper");
 const personSearchInput = document.getElementById(
   "personSearchInputFieldWrapper" );
 const theReactNameApplication = document.getElementById(
   "nameSearchApplicationRoot" );

  // We make a function that will create a portal to mount react components into our seperate DIV elements in the HTML page.

 function PersonSearchWrapper(props) {
   if (personSearchInput == null) {
     return null;
   }
   return ReactDOM.createPortal(
     <PersonSearch consumer={props.consumer} />,
     personSearchInput);
 }
 // We make a function that will create a portal to mount react components into our seperate DIV elements in the HTML page.
 function PersonNameWrapper(props) {
    if (personNameField == null) {
      return null;
    }
   return ReactDOM.createPortal(
     <PersonName consumer={props.consumer} />,
     personNameField);
 }
 // We check if our application div is available and found before mounting.
 if (theReactNameApplication != null) {
   // We render every portal on the application with a middleware to broadcast events using the new React Context API.
   ReactDOM.render(
     <Middleware provider={Provider}>
       <PersonSearchWrapper consumer={Consumer} />
       <PersonNameWrapper consumer={Consumer} />
     </Middleware>,
     theReactNameApplication
   );
 }
});

The middleware looks like the following:

Middleware.jsx:

import React, { Component } from "react";
import { Provider } from "./Context";
// This class is the main state of the sandbox. It holds the state of everything and broadcast the state to other isolated componets.
export default class Middleware extends Component {
 constructor(props) {
   super(props);
   this.state = {
     name: ""
   };
   this.setGlobalState = this.setGlobalState.bind(this);
   this.getGlobalState = this.getGlobalState.bind(this);
   this.updateLabelWithNewName = this.updateLabelWithNewName.bind(this);
 }
 getGlobalState() {
   return this.state;
 }
 setGlobalState(globalStateToSet) {
   this.setState(globalStateToSet);
 }
 updateLabelWithNewName(newName) {
   this.setState({ name: newName });
 }
 render() {
   let _self = this;
   // we render the provider and broadcast state to every child component under it.
   return (
     <Provider
       value={{
         state: _self.state,
         setGlobalState: _self.setGlobalState,
         getGlobalState: _self.getGlobalState,
         updateLabelWithNewName: _self.updateLabelWithNewName
       }}
     >
       {this.props.children}
     </Provider>
   );
 }
}

Take a look at how our Context look like. It has the provider and consumer.

Context.jsx:

// we use react createContext to get the provider and consumer that we used in our Middleware. These will provide the abilities to broadcast and share state between isolated components created by our Portal method in React. 
import { createContext } from "react";
const { Provider, Consumer } = createContext();
export { Consumer, Provider };

I would finally like to show you how the smaller components look like that renders the name field and the search input components.

The code for the component name PersonSearch that will be mounted on a div called #personSearchInputFieldWrapper

PersonSearch.jsx:

import React from "react";
import { Consumer } from "./Context";
const WithContext = Component => {
 return props => (
   <Consumer>
     {contextValue => <PersonSearch {...props} contextValue={contextValue} />}
   </Consumer>
 );
};
class PersonSearch extends React.Component {
 constructor(props) {
   super(props);
   this.updateLabelWithNewName = this.updateLabelWithNewName.bind(this);
   this.state = {
     name: ""
   };
 }
 updateLabelWithNewName(event) {
   this.props.contextValue.updateLabelWithNewName(event.target.value);
   this.setState({
     name: event.target.value
   });
 }
 render() {
   const searchBoxINputFieldStyle = {
     width: "100px"
   };
   let _self = this;
   return (
     <p>
       Name input:
       <input
         type="text"
         value={this.state.name}
         onChange={_self.updateLabelWithNewName}
         style={searchBoxINputFieldStyle}
         placeholder="Write a name"
       />
     </p>
   );
 }
}
export default WithContext(PersonSearch);

PersonName is a component that shows the name you have entered from the above component.

PersonName.jsx:

import React from "react";
import { Consumer } from "./Context";
const WithContext = Component => {
 return props => (
   <Consumer>
     {contextValue => <PersonName {...props} contextValue={contextValue} />}
   </Consumer>
 );
};
class PersonName extends React.Component {
 constructor(props) {
   super(props);
 }
 // For every new global state change, the middleware will reRender its children including this one with a new state.
 // this render method will get the new state from middleware and render the new status.
 render() {
   let style = {
     textDecoration: "underline"
   };
   let globalState = this.props.contextValue.getGlobalState();
   return <div style={style}>Name typed: {globalState.name}</div>;
 }
}
export default WithContext(PersonName);

The HOC Provider

You must have noticed the WithContext method that wraps our React components (PersonName and PersonSearch) with a Consumer. How can you share Context with multiple isolated components? Each provider will have its own distinct value. Consumers will look up to React until it finds a matching provider and use its value. We use portals to render different DOM elements, so that we can manage that while being able to keep our components within the same provider. The WithContext used in these components is called a HOC which will simply inject properties from the middleware to be accessed and shared with your component before render. This will give us better control of state in each isolated component that want to update or read from the global state in our Middleware.

Conclusion

I hope by now you have an idea of how to use the new Context API released in React 16.3 and above. You should now be able to use the new Context API which will simplify your architecture. I still recommend using Redux in much bigger applications than this one. Context API is still good for smaller applications. Consider using it.

Millad Dagdoni

Published by Millad Dagdoni

Programmer, architect

%d bloggers like this: