codeburst

Bursts of code to power through your day. Web Development articles, tutorials, and news.

Follow publication

I hate Redux, so I write my own State Management in the weekend — Part 2

--

Disclaimer: I’m an average React programmer. So I’m not sure my examples are perfect or bug-free.

In my last post, I explained basic functions and concepts of GState. In the post, I’ll follow my opinions with patterns to solve some state management problems:

  1. Unidirectional data flow
  2. State declarative components (Relay/Apollo inspired).
  3. Encapsulation in single state store (Redux issue).

Unidirectional data flow

GState unidirectional data flow

GState could apply the unidirectional data flow like Flux or Redux. Let take the classic Redux’s Todo example and rewrite with GState (Source code):

Commands: Write side in CQRS pattern.

Commands is same with Redux’s reducers. They could be pure functions, take state as parameter, and use state.get to read, state.set to mutate. It is important because you could reuse the command’s business logic for any state context/path. It is also easy for testing:

const state = new GState();state.set({ todos: {} });addToDo(state, "a");expect(state.get({ todos: { _: 1 }}))
.toMatchObject({ todos:[{text: "a", completed: false}]});

Queries: Read site in CQRS pattern

Queries use gstate state declarative high order component(same with Appollo’s graphql or Relay createFragmentContainer) which use state.watch to query information and render stateless components.

State declarative components

Even more important is that these components are declarative: they allow developers to specify what the UI should look like for a given state, and not have to worry about how to show that UI — Thinking in Relay

In our perfect world, component should only care about render a state without knowing how, when and from where.

// Expects the props to have the following shape:
// {
// text,
// isComplete
// }
const TodoItem = ({ text, isComplete }) => {
return (
<View>
<Checkbox checked={isComplete} />
<Text>{text}</Text>
</View>
);
}
}
// gstate is an HOC watch/get declarative state for the component.
const TodoItemWithState = gstate({
text: 1,
isComplete: 1
}, (props, data) => {
return <TodoItem text={data.text} isCompleted={data.isCompleted} />
})

Gstate HOC

This is my trivial version of gstate HOC. It should be a core function of state declarative pattern.

Component Composition

Complex, large state declarative components can be created by composing smaller components. There are 2 approaches:

  • Parent component could declare data shape for both children and itself.
  • Parent use state.path(key) to create sub-state for children which declare their own state requirements.
// Expects a `list` with a string `title`, as well as the information
// for the `<TodoItem>`s (we'll get that next).
const TodoList = ({ state, title, todos }) {
render() {
return (
<View>
<Text>{title}</Text>
{todos.map(key => <TodoItem state={state.path(["todos", key])} />)}
</View>
);
}
}
const TodoListWithState = gstate({
title: 1,
todos: {
_: "$key" // todos.map((item, key) => key)
}
}, (props, data) => (
<TodoList state={props.state} title={data.title} todos={data.todos}/>
))

Notice: it is same pattern with elm view/model composite

view : Model -> Html Msg
view model =
div
[ class "todomvc-wrapper"
, style [ ( "visibility", "hidden" ) ]
]
[ section
[ class "todoapp" ]
[ lazy viewInput model.field // child component with sub state
, lazy2 viewEntries model.visibility model.entries // child component with sub state
, lazy2 viewControls model.visibility model.entries // child component with sub state
]
, infoFooter
]

Encapsulation in single state store

There are couple issues of Redux encapsulation, I try to solve by GState. Overall, GState solve encapsulation by:

  1. Isolated sub state by state.path API.
  2. Pure function commands with state as parameter.

Problem #1: Two States, One Reducer

GState’s commands are pure functions take state as parameter, so it have no problem to reuse with any state tree/context

function incrementCounter(state){
const counter = state.get("counter") || 0;
state.set({ counter: counter+1})
}

Create two Counter component with different state path

<Counter state={state.path("top")}/>   

<Counter state={state.path("bottom")}/>

Counter component apply incrementCounter to it’s own state

const Counter = ({state}) => ( 
<button onClick={() => incrementCounter(state)}>+</button>
)

Problem #2: Listening to other reducers

You could move up to parent state to watch both counter states.

<Counter state={state.path("top")}/>          
<Counter state={state.path("bottom")}/>
state.watch({
top:{ counter: 1 },
bottom: { counter: 1}
}, res => { total = res.top.counter + res.bottom.counter })

Problem #5: Dynamic state instances

Create new state tree for dynamic component is easy.

const AddDynamicCounters = ({ state, numberOfCounters}) => {const counterArr = [];    
for (let i = 0; i < numberOfCounters; i++) {
counterArr.push(i);
}
return (
<div>{counterArr.map(id => <Counter state={state.path(i)}/>)}</div>
)
}

Encapsulate commands with components.

First time try Redux, I wonder how to reuse both my component and reducers. Soon I realize it is not what Redux try to do.

The point that Flux (Redux) move Application State out of Component’s State and Business Logic out of Event Handling. It is cool that you could write application state + business logic without any components. And you could reuse your code for any UI frameworks.

But in some cases, encapsulate both view and state is valid. For example divide a complex application into modules.

You could simply add createCounter command into counter.js module. Then you could reuse the module to create Counter component.

// add a create component commandfunction createCounter(state){   //init state   //return component with query
return gstate({ counter: 1 },() => (
<button onClick={() => incrementCounter(state)}>+</button>
))
}function incrementCounter(state){
const counter = state.get("counter") || 0;
state.set({ counter: counter+1})
}

Finals

Next time, I will research about patterns of Data fetching, Communication between components, Time traveling, SSR and Offline first for PWA.

--

--

Published in codeburst

Bursts of code to power through your day. Web Development articles, tutorials, and news.

No responses yet

Write a response