A New Alternative to Redux for React — For Faster & Lighter Web Development

Ruru Maash
JavaScript in Plain English
5 min readApr 16, 2021

--

I bring to your attention the state management library for React, Preact (weighing only 4.8 Kb). The library is still under development, but you can already try it.

Let’s start with an example of everyone’s favorite TODO organizer. Source code on Github. First, let’s create the main component main.js.

// main.js
import React, { createElement, Component, createContext } from 'react';
import ReactDOM from 'react-dom';
import {Connect, Provider} from './store'
import Input from './InputComp'
import TodoList from './TodoList'
import LoadingComp from './LoadingComp'

const Main = () => (
<Provider>
<h1>Todo:</h1>
<LoadingComp>
<TodoList/>
</LoadingComp>
<hr/>
<Input/>
</Provider>
)

ReactDOM.render(<Main />, document.getElementById("app"));

Next, store. We need the store to initialize the library, and also here we specify all the necessary files with actions. In our example, this is actions.js and actionsSetup.

// store.js

import React, { createElement, Component, createContext } from 'react';
import createStoreFactory from 'redoor';

// Exporting all functions from actions.js и actionsSetup.js
import * as actions from './actions'
import * as actionsSetup from './actionsSetup'

// here we specify the necessary functions of the React library
const createStore = createStoreFactory({
Component,
createContext,
createElement
});

// creating a store as a parameter, you must specify an array of objects
// of all used action functions
const { Provider, Connect } = createStore([
actions,
actionsSetup
]);

export { Provider, Connect };

File with our actions and project status

// actions.js

// each local state can contain its own set of variables
// redoor will automatically add them to the global store
// initState is a reserved variable it can be either an object,
// or a function that returns an object with a state
export const initState = {
todos:[],
value:'',
}
// adding a new task to the array
// the state variable contains the global state
// the args variable depends on the values passed from the component
// the function returns new state variables
export const a_enter = ({state,args}) => {
let {value,todos} = state;
todos.push({
id:(Math.random()+"").substr(2),
value:value,
done:false
});
return {
value:'',
todos
}
}

export const a_done = ({state,args}) => {
let {todos} = state;
let id = args.id;
todos = todos.map(it=>(it.id === id ? (it.done = !it.done, it) : it))
return {
todos
}
}

export const a_delete = ({state,args}) => {
let {todos} = state;
let id = args.id;
todos = todos.filter(it=>it.id !== id)
return {
todos
}
}

Components of views

// InputComp.js
import React from 'react';
import {Connect} from './store'

// redoor adds the cxRun function and all variables to the props
// globally store
const Input = ({cxRun, value})=><label className="input">
Todo:

// here we can change the store directly from the component
<input onChange={e=>cxRun({value:e.target.value})}
value={value}
type="text"
/>

// by clicking, we call the action a_enter from actions.js
<button onClick={e=>cxRun('a_enter')} disabled={!value.length}>
ok
</button>
</label>

// соеденяем с redoor наш компонент и экспортируем
export default Connect(Input);

cxRun can operate in two modes. The first is to directly change the contents of the store, as in the case of a string parameter or calling an action from a file actions.js.

And the last component that outputs the to-do list itself.

// TodoList.js
import React from 'react';
import {Connect} from './store'

const Item = ({cxRun, it, v})=><div className="item">
// we call the a_done action, where we specify as a parameter
// array element in the asense this variable will be called args
<div className="item_txt" onClick={e=>cxRun('a_done',it)}>
{v+1}) {it.done ? <s>{it.value}</s> : <b>{it.value}</b>}
</div>
<div className="item_del" onClick={e=>cxRun('a_delete',it)}>
&times;
</div>
</div>

const TodoList = ({cxRun, todos})=><div className="todos">
{
todos.map((it,v)=><Item key={v} cxRun={cxRun} it={it} v={v}/>)
}
</div>

export default Connect(TodoList);

In our project, there are only two variables in the global store, value, and todos. They are initialized by initState in the file actions.js. initState can be an object, or a function that should return an object with a state. Here it is important to understand that all the states in the action file are placed in a single object and each action has access to any state variables.

Actions are functions that must start with the prefix “ a_ “ or “action”. The name of the action function will be specified as the first parameter when calling cxRun. The input parameter will be an object with the state and args variables.

state — this is the entire global state of the project

args is the second parameter of the call to the cxRun function. In our project, when you click delete, we call cxRun(‘a_delete’, it), where the first argument is the name of the action function, and the second is the element itself, which is what we get in args.

The action should return a new state of the state, which will automatically redraw the components that are connected to the store.

What should I do if the action works asynchronously? To do this, we need to connect the setState method to the local variables of the actions file.js using the bindStateMethods function.

//actions.js
let __setState;
let __getState;

// connecting the methods of working with the state
export const bindStateMethods = (getState, setState) => {
__getState = getState;
__setState = setState;
};

export const a_setup = async ({state,args}) => {
__setState({loading:true});
let data = await loading();
__setState({
loading:false,
todos:data
})
}

Now, when you call the “a_load” action, the download icon will appear before the download starts, and after the data is loaded, the data array will be updated and the download icon will be disabled. If you need to get a global state inside an asynchronous function, you can call __getState, which returns the current state of the state.

Debugger

For debugging, there is a redoor-devtool tool. A debugger is a server that listens to data from the redoor library and passes it to a single page at localhost:8333.

Thus, the debugger can be located not only in another browser but also on another machine. This is especially useful when developing for mobile devices.

installing redoor-devtool:

yarn add redoor-devtool

and in a separate console, run the console debug server

npx redoor-devtool -o

The “-o “ key will open chrome at http://localhost:8333, where the debugger will be.

Conclusion

On my own, I can share that I have already done several projects using this library. It was quite convenient to work with on a project with sockets.

There are of course features of use. For example, you need to remember that all actions are “visible” from all modules. This will not be a problem if you have a clear structure for naming actions. In my projects, I use this naming “a_moduleName_actionName”.

That’s all for now. If you are interested, I will try to write a more detailed review.

More content at plainenglish.io

--

--