Handle the state of ReactJS applications in a simple and efficient way

Fernando Galindez
Talpor
Published in
7 min readFeb 24, 2020

Starting a project from scratch can be a very complicated and cumbersome endeavor, especially when it comes to deciding what structure patterns to follow, and what dependencies will define the nature of the project during its lifetime.

Tools such as create-react-app are always a good starting point, offering a baseline of basic dependencies, while also creating an initial file structure inside a /src/ folder, which will contain the executable code of the application.

Though this is only the tip of the iceberg, especially when you compare it to what a real application running on production usually looks like down the line. Handling dozens of Components at the same time, and the increasing complexity of the State are usually some of the challenges to face when building a large ReactJS web application.

When it comes to managing the state, applications such as Redux come to the rescue, establishing a standard, a predefined structure that helps the coder keep up with the state, maintaining it predictable and consistent, as it further grows in complexity.

Redux is a very comprehensive suite of code, a product of its own versatility and ample reach, which results in a highly verbose code structure when using it, where you have to code dozens of lines just to achieve something as simple as increasing a counter.

On the other hand, ReactJS introduced recently a set of tools called Context API, which help to manage the application state without needing extraneous libraries, by exposing to the programmer special components named Consumers and Providers. Later they also released a library named Hooks, which allows you to access the state and other React features without writing any classes.

But how do you put all of this to good use? Is there a new implementation standard now? What’s the best way to maintain the state of my application? These are just some of the questions that could come up when starting to work with ReactJS, or simply when you feel you haven’t caught up on the latest trends when it comes to this technology.

Considering all of this, and trying to make the best out of all the previous successful models, such as Redux’s, we’ve built a suite called React Context Manager, which helps you creating and keeping up with the managing of the state of your ReactJS application.

What’s React Context Manager?

React Context Manager is a set of two libraries:

  • react-context-manager it must be installed locally, and it helps you connect your Store to your components.
  • react-context-manager-cli it must be installed globally, and it handles the creation of the basic file structures for your store.

How to use them?

First, you must create your project, for this, we recommend using create-react-app. It can be either on Javascript or Typescript. We chose Typescript for this example:

npx create-react-app hello-project --template typescript

This will create for you a file structure similar to this one:

hello-project
README.md
node_modules/
package.json
public/
index.html
favicon.ico
src/
App.css
App.ts
App.test.ts
index.css
index.ts
logo.svg

Then, you must install react-context-manager:

npm install --save @talpor/react-context-manager
or
yarn add @talpor/react-context-manager

You can check the full documentation for this library here.

Then, you must install the CLI:

npm install -g @talpor/react-context-manager-cli

You can check the full documentation for this library here.

Initialization

Once everything’s installed, run this command from the root folder of your application:

react-context-manager-cli init

or, using the shorthand:

rcmc init

This will create a basic structure for your store inside the /src/ folder:

  • A /store folder
  • An index.[js|ts] file
/** This is a auto-generated file, please do not modify it*/ 
import { GlobalStore, initContext, Modifiers } from '@talpor/react-context-manager';
export interface IStore extends GlobalStore {}export interface IActions extends Modifiers<IStore> {}const store: IStore = {}const actions: IActions = {}const ctx = initContext<IStore, IActions>();export { actions, ctx, store };

Connecting the store

Now you have to import ContextProvider in the index.tsx file of the /src/ folder. You also have to import the actions, the store and pass them down to the ContextProvider.

The main goal here is to have the context available to every children component, though here for demonstration purposes we are setting it at the highest level, so we can have a global context:

import React from 'react';
import ReactDOM from 'react-dom';
/** import the next two lines */
import { ContextProvider } from '@talpor/react-context-manager';
import { actions, ctx, store } from './store';
ReactDOM.render(
/** wrap the <App /> component inside the ContextProvider */
<ContextProvider actions={actions} context={ctx} store={store}>
<App />
</ContextProvider>,
document.getElementById('root')
);

Creating a sub-store

Having connected the actions to the store, we now need to populate said structure with data. For this, let’s create a subStore called User, and for that, we run rcmc create-store user. This will create a user folder inside the store folder with:

  • store.ts
  • actions.ts
  • mocks.ts
  • tests.ts

After creating said structure, the tool will ask you if you want to recreate the index.ts of the store. We confirm, and now the store is updated with the new addition. Every time you add a new subStore the store needs to be updated.

Important caveat: any manual changes done to the file will be lost after regenerating it via CLI.

actions.ts example

import { Scope } from '@talpor/react-context-manager';import { IStore } from '../index';export interface IUserActions extends Scope<IStore> {
userAction: (state: IStore) => () => IStore;
/** Add your others USER actions types here */
}
export const userActions: IUserActions = {
myUserAction: (state: IStore) => () => {
/** Do your logic here */
return {
...state,
user: {
...state.user,
/** Your modified USER store */
},
/** Any other scope from your store */
};
}
/** You can add other USER actions here */
};

store.ts example

export interface IUserStore {}export const userStore: IUserStore = {}

index.ts example re-generated

/** This is a auto-generated file, please do not modify it*/ 
import { GlobalStore, initContext, Modifiers } from '@talpor/react-context-manager';
import { userStore, IUserStore } from "./user/store"
import { userActions, IUserActions } from "./user/actions"
export interface IStore {
user: IUserStore
}
export interface IActions {
user: IUserActions
}
const store: IStore = {
user: userStore,
}
const actions: IActions = {
user: userActions,
}
const ctx = initContext<IStore, IActions>();export { actions, ctx, store };

Adding some logic

Let’s add an element to the store:

export interface IUserStore {
nameHook: string;
nameHoc: string;
}
export const userStore = {
nameHook: '',
nameHoc: ''
}

We will now create an action called setName:

export interface IUserActions extends Scope<IStore> {
setName: (
state: IStore
) => (name: string, component: 'nameHook' | 'nameHoc') => IStore;
}
export const userActions: IUserActions = {
setName: (state: IStore) => (
name: string,
component: 'nameHook' | 'nameHoc'
) => {
if (name === 'John Doe') {
name = 'Jane Doe';
} else {
name = 'John Doe';
}
return {
...state,
user: {
...state.user,
[component]: name
}
};
}
};

This action will toggle the value of the name between ‘John Doe’ and ‘Jane Doe’.

Creating components to connect to the store

Now we must connect components to this newly created store, and for this react-context-manager comes in handy.

For demonstration purposes, let’s create two components, which will live inside an appropriately named components folder, inside /src/, at the same level as the store. One component will use Hooks and the other will be a Class Component. Let’s say the first one is called Hook. In it we must import ctx from our store and then use React’s useContext:

Function Component “Hook” example with useContext()

import React, { useContext, useEffect } from 'react';
import { ctx } from '../store';
function Hook() {
const store = useContext(ctx.store);
const actions = useContext(ctx.actions);
useEffect(() => {
if (!store.user.nameHook) {
actions.user.setName('John Doe', 'nameHook');
}
}, [actions.user, store.user.nameHook]);
return (
<div className="Hook">
<header className="Hook-header">
<h2>HOOK COMPONENT</h2>
<h3
style={{
color: store.user.nameHook === 'John Doe' ? 'blue' : 'purple'
}}
>
{store.user.nameHook}
<br />
</h3>
<button
className="Hook-link"
onClick={() => {
actions.user.setName(store.user.nameHook, 'nameHook');
}}
>
Change Name
</button>
</header>
</div>
);
}
export default Hook;

We now create a class component called Hoc, which instead imports mapContextToProps:

import React from 'react';
import { ctx } from '../store';
import { mapContextToProps } from '@talpor/react-context-manager';class Home extends React.Component<any, any> {
componentDidMount() {
this.props.actions.user.setName('John Doe', 'nameHoc');
}
render() {
const { actions, store } = this.props;
return (
<div className="Home">
<header className="Home-header">
<h2>HOC COMPONENT</h2>
<h3
style={{
color: store.user.nameHoc === 'John Doe' ? 'blue' : 'purple'
}}
>
{store.user.nameHoc}
<br />
</h3>
<button
className="Home-link"
onClick={() => {
actions.user.setName(store.user.nameHoc, 'nameHoc');
}}
>
Change Name
</button>
</header>
</div>
);
}
}
export default mapContextToProps(ctx)(Home)('user');

Passing components to the App.tsx

Finally, let’s integrate our newly created components to our main App component:

import React from 'react';
import './App.css';
import Hoc from './components/homeHoc';
import Hook from './components/homeHook';
const App = () => {
return (
<div className="App">
<Hoc />
<Hook />
</div>
);
};
export default App;

Result

With this, we now have a fully functional example of how to manage the state of a ReactJS application using react-context-manager, which helps to standardize and automating a lot of the busywork around such a burdensome task.

Author

Contributors

--

--