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

Fernando Galindez
Feb 24 · 7 min read
Image for post
Image for post

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.

Image for post
Image for post

What’s React Context Manager?

  • 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?

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

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

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

  • 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

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

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

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

Image for post
Image for post

Author

Contributors

Talpor

We build fantastic digital products for startups and major brands.Let’s build something big together

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch

Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore

Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store