Easiest Approach to React App State — using Redux without writing Redux

Arnel Enero
5 min readFeb 3, 2019

--

Now I hear you say ‘what was that again?’. Well, there is now a way to implement your application state management in Redux even if you don’t write a single line of Redux boilerplate code. You don’t even need to know or learn Redux. All you need to know is that it’s Redux working behind the scenes. As long as you appreciate that Redux is the best solution to your app state needs, then you will want to read this.

The Reactor Library

I originally wrote the Reactor Library to minimize the boilerplate needed in my personal projects that use React. One of its features is the super simple app state management that I will share with you here.

I have since decided to make the library available to everyone who may be looking to reduce their boilerplate code. It is now on NPM: https://www.npmjs.com/package/@reactorlib/core

The 3 Key Things

To write our application state management using Reactor Library, there are 3 key things we need to know about:

  • Store: This is the single place where the entire state of our application is kept.
  • Entities: These are pieces of the app state, each representing a specific area of concern or functionality.
  • Actions: These are functions that our components can invoke to trigger some change in the app state. These also reside in the store.

Step 1: Creating Entities

When we define an entity, we think about how the entity would react to certain actions. We refer to this as its reactions. Each reaction comprises state changes that occur within the entity (remember, each entity is just a portion of our app state).

Reactor Library provides a function called createEntity that we will use to define our entities. It accepts two arguments, the entity’s reactions, as well as its initial state:

createEntity(reactions: Object, initialState: any)

Let’s get the easier part out of the way first. The initialState should basically define the data structure of our entity by assigning a default value to it.

The reactions argument is a mapping of action names against corresponding reactions. Note that the mapping is not meant to define the actual action functions.

In its simplest form, a reaction looks like this:

action: (state, payload) => newState

where action corresponds to the name of an action, while payload (optional) is any single argument that the entity expects you to pass to the action. All this really means is, when action(payload) is invoked, the entity applies certain logic to change its state from state to newState.

Here is a simple example of entity definition:

const initialState = { value: 0 };

const counter = createEntity(
{
increment: (state, by) => (
{ ...state, value: state.value + by }
),
reset: state => ({ ...state, value: 0 })
},
initialState
);

IMPORTANT: In defining an entity’s reactions, keep in mind that the React golden rule of not mutating the component state also applies to the application state. So if your entity’s state is of object or array type, always make sure to return a fresh object or array.

Easy peasy so far, right? Let’s go on…

Step 2: Setting Up the Store

I said ‘the store’ because there can only be one store throughout our entire application. To make this store available to all our components, we would need to inject this into a top-level component, typically <App>.

Reactor Library includes the withStore HOC that creates the store, puts entities into it, and designates its target component as the provider/owner of the store.

withStore(entities: Object) (Component)

Here the entities argument is a mapping of entity names against the actual entity objects created using createEntity(). This mapping is important because we access entities from the store using the names assigned here.

Let’s take the counter entity from our previous example, and create our store then place the entity in it:

import counter from './store/counter';

const _App = () => (
<Router>
<Shell />
</Router>
);

const App = withStore({ counter })(_App);

As simple as that, really. Our store is now all set.

Step 3: Importing Props from Store

Now the last remaining step is to make the application state accessible to our components. There are 2 simple rules:

  • Components are able to read the application state by importing entities from the store.
  • They can also change the app state, by importing actions from the store.

We use Reactor Library’s getPropsFromStore HOC to do either or both, and inject them to our component as props.

getPropsFromStore(
entities?: Array<string>,
actions?: Array<string>
) (Component)

Here, entities is a list of entity names, and actions is a list of action names.

Imported entities are injected as state props. This means that whenever any of these entities change, the component will re-render.

Imported actions are injected as function props that we can directly invoke inside our component.

You may be wondering, where do we define these action functions? Well, we don’t. The store creates these for us, based on all the action names we mapped to the reactions when creating our entities with createEntity.

Continuing our previous examples, we import the counter entity from the store as follows:

const _ClickCount = ({ counter, increment, reset }) => (
<>
You have clicked {counter.value} times.
<button onClick={() => increment(1)}>Click Me</button>
<button onClick={reset}>Reset Counter</button>
</>
);

const ClickCount = getPropsFromStore(
['counter'],
['increment', 'reset']
)(_ClickCount);

That’s it! In 3 easy steps, we have connected our component to the app state.

So What Next?

Now that you know how Reactor Library can simplify managing the app state, you may also want to know about its more advanced use-cases. Check out these links to find out more:

  • Defining async actions declaratively — if you do data fetches, timers, and other async side-effects, you will want to read this.
  • Working with lazy loaded entities — if you do code splitting, you will want to code-split your application state as well. Read this. (coming soon)
  • Reactor Library official documentation — learn about using selectors when importing from store, using middleware and store enhancers, Redux dev tools support, etc.

Beyond App State Management

Easy peasy app state management is just one of several essentials that Reactor Library provides, that make writing your React app a lot simpler. Feel free to check out its documentation to find out what more it has to offer: https://github.com/arnelenero/reactorlib#reactor-library.

A Little Disclaimer

Some hardcore fans of Redux might ask, isn’t all the stuff I wrote above just Redux but with a different syntax? The quick answer is yes. When I originally wrote this library for my personal use, I just wanted the quickest possible approach, requiring the least amount of boilerplate code, but the same power provided by Redux. So expressing Redux in a different format is the whole point.

This library is for you if either:

  • You have never written Redux code before and/or haven’t learned it yet
  • You know Redux but are looking for alternative ways of working with it

There is an official helper library from the creators of Redux, too. So if you wish to keep your code expressed in Redux terms like reducers, etc., you may want to use that instead. Still slightly more boilerplate than what’s presented here, maybe, but it’s as official as it gets.

And one final point I’d like to make: Reactor Library is not just app state management, so even if you don’t use it for Redux, there may be other ways it can help you with React app development. Feel free to use it, it’s yours as much as mine.

--

--

Arnel Enero

Web architect, tech buff, audio/acoustics geek, car enthusiast