Restate — the story of Redux Tree

Written by antonkorzunov | Published 2018/01/04
Tech Story Tags: react | redux | restate | composition

TLDRvia the TL;DR App

Redux — the holy cow for modern React Application. But is it enought Reactish? To say the truth — no.

https://blog.codecentric.de/en/2017/12/developing-modern-offline-apps-reactjs-redux-electron-part-3-reactjs-redux-basics/

“Reactish” TODO-list

Lets try to build iconic TODO-list application using React only.

code is listen in a reverse order

// first - create a underlying data structureconst TODOs = [todo1, todo2, todo3];

// second pass into Applicationconst Application = <TodoList todos={TODOs} />

// third define todo listconst TodoList = ({todos}) => (<ol>{todos.map( todo => <Todo key={todo.id} {...todo} /></ol>);// last define Todoconst Todo = (props) => <div>......</div>

The key feature here is — All the data for a child was passed by it’s parent.

“Redux” TODO-list

Lets try to rework this into redux variant

This is simplified version of the original Redux’s Todo list

// first - create a underlying data structureconst store = createStore({todos: [todo1, todo2, todo3]});

// second pass into Applicationconst Application =<Provider store={store}><ConnectedTodoList/></Provider>

// third connect todo listconst ConnectedTodoList = connect(state => ({todos: state.todos}) // <- here we inject the data)(TodoList)

// forth define todo listconst TodoList = ({todos}) => (<ol>{todos.map( todo => <Todo key={todo.id} {...todo} /></ol>);// last define Todoconst Todo = (props) => <div>......</div>

The key feature here is — at any point you could connect to the global store. Get data, and dispatch events. This decouples a whole application, but, actually, makes parts of an Application less composable.

Even more — Component composition (another holy cow of React) just will not work, as long redux ignores it’s position in time and space.

A bit more “redux-ish” TODO-list

So. I could mention the one moment here, which could be improved. TodoList is too reactish. And Todo is absolutely not reduxish. Lets fix it

....// third connect todo listconst ConnectedTodoList = connect(state => ({todos: idsFrom(state[todos])})// only ID, not all data)(TodoList)...// forth define todo listconst TodoList = ({todos}) => (<ol>{todos.map( todo => <ConnectedTodo key={todo.id} id={todo.id} /></ol>);

// use props to get the desired dataconst ConnectedTodoList = connect((state, props) => ({...state.todos[props.id]}) // get the TODO)(Todo)

// last define Todoconst Todo = (props) => <div>......</div>

This is faaaaar more better. TodoList is invariant to any change in Todo’s data, and TODO element is connected directly to the store.

This is also quite cool, cos it creates borders for an event propagation.

Redux’s connect is the start for any update, as long it connected directly to the store, and triggered directly.

Redux’s connect is the end for any update, as long it is a PureComponent, and will pass only changes, inducted by the Redux Store, not React update.

It’s like an octopus, handling and drowning your app.

Think about the origins of things. Beginning, and ending.

The final battle

Lets pick another example. Almost todo list, but with nesting. Here is the link to the sources.

reactjs/redux_redux - Predictable state container for JavaScript apps_github.com

The key feature here — you got a Node, and that node got a list inside. And to update/edit that list you have to specify nodeId, not only listId.

And redux failed to purely solve this problem. They could not “normally” provide dispatch with nodeId prefilled, and they shall not provide nodeId for List, to let List select data from the store.

What they could do? They use React handlers to do Redux work.

handleAddChildClick = e => {// get nodeId and dispatch function from a parentconst { addChild, createNode, id } = this.props;const childId = createNode().nodeId;// dispatch event with both variables knownaddChild(id, childId)}

This breaks component approach. This is fail.

Restate to the rescue

Redux-restate, Redux-tree, re-store, Redux-Lenses, Transformation… redux is missing all these things, and Restate is all these things.

Restate is a brand new library, which enables your application to become both more Reactish, and more Reduxish. And solves the problems of the last example.

How? Lets create an example:

....// third connect todo listconst ConnectedTodoList = connect(state => ({todos: idsFrom(state[todos])})// only ID, not all data)(TodoList)...// forth define todo list Mapperconst TodoList = ({todos}) => (<ol>{todos.map( todo => <ReconnectedTodo key={todo.id} id={todo.id} /></ol>);

// create a new redux connection point (HERE IS THE MAGIC)// "focus" into the derived state

const ReconnectedTodo = reduxFocus(// keep only selected TODO in the store(state, pros) => { todo: state.todos[id] },// `restore` TODO id is on dispatch(dispatch, event, props) => dispatch({...event, id: props.id}))(ConnectedTodo);

// use props to get the desired dataconst ConnectedTodoList = connect((state) => ({...state.todo}) // a single todo HERE, the right one)(Todo)

// last define Todoconst Todo = (props) => <div>......</div>

PS: reduxFocus is a part of Redux Restate.

This example uses react-redux-focus — this simpler version of react-redux-restate, which is a “react” version of redux-restate.

This solves main redux (global state) issue: reusability.

Get the control, form sub-states, sub-stores. Map and reduce. Compose.

May be you already have a connected component, tightly bound to some state structure just by fetching some keys from the state? You know — that component is not reusable. Throw it away

const ConnectedXComponent = connect(mapStateToProps)(MyXComponent);

....// just wrap it with redux state "adapter". Why not?

const ReusableXConnectedComponent = reduxFocus((state,props) => createXState(state, props))(ConnectedXComponent)

What it does?

reduxFocus creates the new store from the current one, focusing the “bigger” state for the children. ConnectedTodo will have to accept only a single TODO, and it will be isolated from any other data, it does not need. (ie will be focused)

In the same time, as long reduxFocus scopes the data, there is no need to pass down “id”, to use it in dispatch, as long you could augment all events passing back to the original store with todoId you know.

Thus also means, that you could place TODO-list in a Trello-like tree, just define lense to get a single TODO-list from a “big” store, and place “nodeid” to all the events, a nested component will dispatch in a future.

This will enable Component model, so beloved by everyone.

More complex example?

React-redux-focus is a simple HOC, designed to work with a single store. The original React-redux-restate is designed to work with more the one store. But why??

const mapStateToProps = state => {something: memoizedWithReselect(someComplexOperation(state))}connect(mapStateToProps)(Component)

How often Component’s render method be called? Everytime some real update will pass memoization, and trigger update. So — not often.

How often someComplexOperation will be called? Everytime state got updated. Even if you will also memoize it — you will call memoized function… a lot. From ReactRedux render method.

If you have 100500 connection to the store — all of them will call mapStateToProps on every store update. Unless you will specify areStatesEqual, but you will not.

Restate IS that areStatesEqual — a “lensed” small part of a global store for the children, and isolate them from the rest.

// default store will be accessible as `default`.const NestedStore = restate({}, ({ default:state }) => ({ state.only, state.data, state.i, state.need }))(RenderChildren);

But next you might need the original data once again. But also might need the computed data from the current, sintetic store.

const Reprovider = reprovider('superStore'); // copy store to the superStore

const NestedNestedStore = restate({ base: 'superStore' }, // get `superStore` as `base`({ default: state, base }) => ({ ...state, base.dataFromBase})(dispatchers, event, props) => {dispatch dispatches.default or dispatcher.base?})...

This is not the best example, but sometimes it is possible to have to stores in a single application, for example “Application” and “Page”, and work restate will enable you to combine the data from both of them, and route dispatch back to the correct store.

Even more — two stores is common way to work with MobX

Is it still redux?

The good question, as long few things may break the core concepts.

  1. Is it ok to create derivered store from a store?

In database world it called views or mat-views(as long they are memoized). You do the same with mapStateToProps. It is ok from a theory point of view.

2. Is it ok to route dispatches back? Bubbling yet again?

First — there is no other way. Second — as long routeDispatch is a pure function (same as mapStateToProps or reducers) — it is _predictable_. As long store behavior is predictable — it is ok.

3. Is it ok to make containers sensitive to the place in a tree?

This part was missing. With restate application can use redux in a better way — more often, more easier, more correct.

4. Is it a real redux store?

No. This is just a view with redux-like public interface, reusable by redux’s connect down the tree. It’s fake.

The current way to solve some sort of tasks is just not correct. Recall the Tree-example — not React, not Redux, just hacks around.

You don’t need Redux?

It could sounds a bit odd, but Restate is ok without Redux. You can merge one or more states together, or you can form a new state from props, mapping dispatch to React’s component methods.

return (/* <ReduxFocus focus={bigState => smallState}> */<ReduxFocus focus={this.getState}><ReduxDelay timeout={500}>counter: <Counter /> /* will connect to "redux" */</ReduxDelay></ReduxFocus>);

So restate — is a powerfull state management library. Fully React and Redux ideologically compatible.

Time to try?

Restate was releases literally yesterday, but it is ready. The original repo contains more examples, documentation, and a bit more theory about.

theKashey/restate_restate - Make redux composable again!_github.com

Let redux be composable again! Let the component architecture bloom!

I’ve got a 3(already 5) package for you. Pick the one you need:

// to low-level redux manupulationsimport reduxRestate from 'redux-restate';

// to work with multiple storesimport reactReduxRestate from 'react-redux-restate';

// to focus a lens on a single storeimport reactReduxFocus from 'react-redux-focus';

// to freeze the timeimport reactReduxSemaphore from 'react-redux-semaphore';

// to optimize state changesimport reactReduxDelay from 'react-redux-delay';

// to optimize update propagationimport reactReduxUnbranch from 'react-redux-unbranch';

PS: The actual code is just 10–50 LoC per componet. Somethings might become more clear, if you will just read the code.

Follow up article

The State of the State of the State_In other words — Fractal State. To be more concrete — Redux Fractal State. It is possible to create Fractal using…_blog.cloudboost.io


Published by HackerNoon on 2018/01/04