
Let's consider a simple ToDo
This is what your colleague left you as a gift 💩 before going on holiday:
Inindex.js
there is a mess of functions within components passed between components that call other functions.
And if it were a real project it would be MUCH MUCH worse
because the COMPONENT TREE isdeeper
and the nesting of COMPONENTs multiplies the complexity!
Let's create a STORE
Let us try to extrapolate the LOGIC and STATUS from the COMPONENTs to put it into a STORE.
constmyStore={callbacks:newSet(),subscribe:(callback)=>{myStore.callbacks.add(callback)return()=>myStore.callbacks.delete(callback)},getSnapshot:()=>myStore.state,changeState:(newState)=>{myStore.state=newStatemyStore.callbacks.forEach(cb=>cb())},}
It is a generic implementation of a STORE usinguseSyncExternalStore
the new React18 hook (but it can also be done with React17!).
therefore:
subscribe
Stores a callback to be called when the STATE of the STORE is changedgetSnapshot
Returns the current STATEchangeState
It is convenient to exchange one STATE for another STATE.Remember that a STATE is immutable!And notify all registeredcallbacks
of the change
Enter the STATE
constmyStore={...state:{todos:[{desc:"init value"},],todoInEdit:{desc:""},},...}
It is a picture of what our VIEW, in this case the ToDo app, should look like.
A STATE represents one and only one view of the VIEW.
Enter the MUTATORS
constmyStore={...setTodos:todos=>myStore.changeState({...myStore.state,todos}),setTodoInEditProp:prop=>myStore.changeState({...myStore.state,todoInEdit:{...myStore.state.todoInEdit,...prop}}),...}
They simply execute thechangeState
by passing it the modified STATE.
Consequently, they will notify the changes to the COMPONENTS.
(as said before)
Let's add the ACTIONS
constmyStore={...deleteTodo:(index)=>{constnewTodos=myStore.state.todos.filter((_,i)=>i!==index)myStore.setTodos(newTodos)},addTodoInEdit:()=>{constnewTodos=[...myStore.state.todos,myStore.state.todoInEdit]myStore.setTodos(newTodos)myStore.setTodoInEditProp({desc:""})},...}
Let's update the VIEW
functionApp(){return(<div><List/><Form/></div>);}functionList(){conststate=useSyncExternalStore(store.subscribe,store.getSnapshot)return(<ul>{state.todos.map((td,index)=>(<li>{td.desc}<buttononClick={_=>store.deleteTodo(index)}> Delete</button></li>))}</ul>)}functionForm(){conststate=useSyncExternalStore(store.subscribe,store.getSnapshot)consthandleChange=e=>store.setTodoInEditProp({desc:e.target.value})consthandleClickAdd=_=>store.addTodoInEdit()return(<div><inputvalue={state.todoInEdit.desc}onChange={handleChange}/><buttononClick={handleClickAdd}>Add</button></div>)}
The STORE is responsible for managing the LOGIC and the STATE
the VIEW simply synchronises with the STORE
The COMPONENTS are more readable
and can be moved without problems.
For example,List
can be put inside another component without changing anything
because it is no longer "dependent" on its PARENT
In factList
has no properties
this also makes unit-testing easier.
In short, if I have to change the behavior I have to look at the STOREs.
If I have to change the display I will have to act on the COMPONENTS
Jon
There are many libraries in React (as usual) that allow STATE management.
Of course I made one 😃
In my opinion, compared to the others, it allows
1) to see perfectly how it works under the hood. NO MAGIC
2) is super light
3) does only this, and nothing else
If you want, check it outhere
Top comments(0)
For further actions, you may consider blocking this person and/orreporting abuse