Full State Management in React (without Redux)
Tags: react
Published: 2020-09-14 10:12:15

#Motivation
Having an `App` component having this child:
```javascript

            <Counterss name={name1} liftUp={catch1}/>
```
and the `Counterss` component having these children:
```javascript

        <Counters liftUp={catch1} name={name+'-'+name1}/>
        <Counters liftUp={catch2} name={name+'-'+name2}/>
```
and the `Counters` component having these children:
```javascript

            <Counter liftUp={catch1}
            name={name+'-'+name1}/>
            <Counter liftUp={catch2}
            name={name+'-'+name2}/>
```
I want this:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/x3cdc21s7ht5ml89lv96.png)
that's it, I want full control of my state. I want each component to have a local state defined through the use of `useReducer` and I want a `store` object where I can access all these local states of all the components, starting from the `App` component to the innermost component, from anywhere in the app, in any component, from the outermost to the innermost.
I want to use `useContext` to get access to this `store` object in order to be able to use `dispatch` and `state` of any local state of the components of the app anywhere and I want it to be reactive.
For that purpose I need named components, that's it, I must pass a property named `name` to each component when I use it in the app.
Also, I need what is found in the article [lifting up information in React from one component to its parents in the component tree](https://dev.to/roggc/lifting-up-information-in-react-from-one-component-to-its-parents-in-the-component-tree-51p) because the strategy I will follow will be to lift up all info, that's it `state` and `dispatch` of every local state, and then make it accessible to all the components through a `store` object defined in the `App` component with the use of `useContext`.
#The *HOC*
The *HOC* I will be using it's a variant of the one defined in the post mentioned above. Because one component can have more than one child, I am interested in catching up all the info of all of the children, so I define the *HOC* like this:
```javascript
import React,{useState,useRef} from 'react'

export default C=>(props)=>{
    const [foo,setFoo]=useState(0)
    const info1=useRef(null)
    const catch1=(info)=>{
        info1.current=info
        setFoo(prev=>prev+1)
    }
    const info2=useRef(null)
    const catch2=(info)=>{
        info2.current=info
        setFoo(prev=>prev+1)
    }
    const info3=useRef(null)
    const catch3=(info)=>{
        info3.current=info
        setFoo(prev=>prev+1)
    }
    const info4=useRef(null)
    const catch4=(info)=>{
        info4.current=info
        setFoo(prev=>prev+1)
    }
    const info5=useRef(null)
    const catch5=(info)=>{
        info5.current=info
        setFoo(prev=>prev+1)
    }
    const info6=useRef(null)
    const catch6=(info)=>{
        info6.current=info
        setFoo(prev=>prev+1)
    }
    const info7=useRef(null)
    const catch7=(info)=>{
        info7.current=info
        setFoo(prev=>prev+1)
    }
    const info8=useRef(null)
    const catch8=(info)=>{
        info8.current=info
        setFoo(prev=>prev+1)
    }
    const info9=useRef(null)
    const catch9=(info)=>{
        info9.current=info
        setFoo(prev=>prev+1)
    }
    const info10=useRef(null)
    const catch10=(info)=>{
        info10.current=info
        setFoo(prev=>prev+1)
    }
    return (
        <C 
        catch1={catch1} 
        catch2={catch2} 
        catch3={catch3} 
        catch4={catch4}
        catch5={catch5} 
        catch6={catch6}
        catch7={catch7} 
        catch8={catch8} 
        catch9={catch9} 
        catch10={catch10} 
        info1={info1} 
        info2={info2} 
        info3={info3} 
        info4={info4} 
        info5={info5} 
        info6={info6} 
        info7={info7} 
        info8={info8} 
        info9={info9} 
        info10={info10}
        {...props}/>
    )
}
```
With the use of this *HOC* I can have up to ten children in each component. If there is a component that has more than ten then I would need to modify the *HOC* in order to put the capacity for catching info from more children.
#The innermost component
Let's take a look to the definition of the innermost component:
```javascript
import React,{useEffect,useReducer,useContext} from 'react'
import {reducer,initialState} from './reducer'
import {StoreContext} from '../App'

const Counter=({liftUp,name})=>{
    const names=name.split('-')
    const store=useContext(StoreContext)

    const [state,dispatch]=useReducer(reducer,initialState)

    useEffect(()=>{
        liftUp.bind(null,{state,dispatch})()
    },[state])

    return (
        <div>
            {store[names[0]]&&store[names[0]][names[1]]&&
            store[names[0]][names[1]][names[2]].state.counter}
        </div>
    )
}

export default Counter
```
As you can see it's a counter component because it defines a `state` and a `dispatch` function which are as follows:
```javascript
import {INCREMENT,DECREMENT} from './actions'

export const initialState={
    counter:0
}

const increment=(state,action)=>{
    return {
        ...state,
        counter:state.counter+1
    }
}

const decrement=(state,action)=>{
    return {
        ...state,
        counter:state.counter-1
    }
}

export const reducer=(state,action)=>{
    switch(action.type){
        case INCREMENT:
            return increment(state,action)
        case DECREMENT:
            return decrement(state,action)
        default:
            return state
    }
}
```
So you see how we have an initial state with `counter` set to zero, and then operations to increment and decrement that counter.
The `Counter` component receives a `liftUp` property. This is used to lift up info to the parent component of `Counter`. We do that in a `useEffect` hook, binding to the `liftUp` function an object with the info we want to attach, and calling it.
```javascript

    useEffect(()=>{
        liftUp.bind(null,{state,dispatch})()
    },[state])
```
#The `Counters` component
Now let's take a look at the definition of the `Counters` component, the parent of the `Counter` component, or at least one that have `Counter` components as a child.
```javascript
import React,{useReducer,useState,useRef,useEffect,useContext} from 'react'
import Counter from '../Counter'
import * as styles from './index.module.css'
import * as counterActions from '../Counter/actions'
import {reducer,initialState} from './reducer'
import {StoreContext} from '../App'
import withLiftUp from '../../hocs/withLiftUp'

const Counters=({liftUp,name,catch1,catch2,info1,info2})=>{
    const names=name.split('-')
    const store=useContext(StoreContext)
    const [state,dispatch]=useReducer(reducer,initialState)

    const increment1=()=>{
        console.log(store)
        store[names[0]][names[1]][name1].dispatch(counterActions.increment())
    }
    const decrement1=()=>{
        store[names[0]][names[1]][name1].dispatch(counterActions.decrement())
    }

    const increment2=()=>{
        store[names[0]][names[1]][name2].dispatch(counterActions.increment())
    }
    const decrement2=()=>{
        store[names[0]][names[1]][name2].dispatch(counterActions.decrement())
    }

    const name1='counter1'
    const name2='counter2'

    useEffect(()=>{
        liftUp.bind(null,{
            state,dispatch,[name1]:info1.current,[name2]:info2.current
        })()
    },[state,info1.current,info2.current])

    return (
                <div>
            <Counter liftUp={catch1}
            name={name+'-'+name1}/>
            <Counter liftUp={catch2}
            name={name+'-'+name2}/>
            <div>
                <button onClick={increment1}>increment</button><br/>
                <button onClick={decrement1}>decrement</button><br/>
                {store[names[0]]&&store[names[0]][names[1]]&&
                store[names[0]][names[1]][name1]&&store[names[0]][names[1]][name1].state.counter}
            </div>
            <div>
                <button onClick={increment2}>increment</button><br/>
                <button onClick={decrement2}>decrement</button><br/>
                {store[names[0]]&&store[names[0]][names[1]]&&
                store[names[0]][names[1]][name2]&&store[names[0]][names[1]][name2].state.counter}
            </div>
        </div>
    )
}

export default withLiftUp(Counters)
```
The first thing we notice are the `catch1`, `catch2`, `info1`, and `info2` properties we receive:
```javascript
const Counters=({liftUp,name,catch1,catch2,info1,info2})=>{
```
That's because we make use of the `withLiftUp` *HOC* defined earlier and because we have to children in this component from where we want to get info, that's it:
```javascript
            <Counter liftUp={catch1}
            name={name+'-'+name1}/>
            <Counter liftUp={catch2}
            name={name+'-'+name2}/>
```
You see how we pass to the children a property named `liftUp` with the `catch1` and `catch2` functions the *HOC* gives to us.
We then have this:
```javascript
    const name1='counter1'
    const name2='counter2'

    useEffect(()=>{
        liftUp.bind(null,{
            state,dispatch,[name1]:info1.current,[name2]:info2.current
        })()
    },[state,info1.current,info2.current])
```
We are passing up the info from the childs. The info from the children will be contained in `info1.current` and `info2.current` because `info1` and `info2` are refs. Take a look at the post mentioned earlier if this is not clear to you.
Don't pay attention now at the names. We are going up through the tree. Later we will go down and will take into account the names.
#The `Counterss` component
This component has as children instances of the `Counters` component:
```javascript
import React,{useReducer,useContext,useEffect} from 'react'
import Counters from '../Counters'
import {reducer,initialState} from './reducer'
import withLiftUp from '../../hocs/withLiftUp'
import {StoreContext} from '../App'

const Counterss=({catch1,catch2,info1,info2,name,liftUp})=>{
    const names=name.split('-')
    const store=useContext(StoreContext)
    const [state,dispatch]=useReducer(reducer,initialState)
    
    const name1='counters1'
    const name2='counters2'

    useEffect(()=>{
        liftUp.bind(null,{state,dispatch,
        [name1]:info1.current,[name2]:info2.current})()
    },[state,dispatch,info1.current,info2.current])

    return (
        <div>
        <Counters liftUp={catch1} name={name+'-'+name1}/>
        <Counters liftUp={catch2} name={name+'-'+name2}/>
        {store[names[0]]&&
        store[names[0]][name1]&&store[names[0]][name1].counter1.state.counter}
        {store[names[0]]&&
        store[names[0]][name1]&&store[names[0]][name1].counter2.state.counter}
        {store[names[0]]&&
        store[names[0]][name2]&&store[names[0]][name2].counter1.state.counter}
        {store[names[0]]&&
        store[names[0]][name2]&&store[names[0]][name2].counter2.state.counter}
        </div>
    )
}

export default withLiftUp(Counterss)
```
You notice how we receive those props:
```javascript
const Counterss=({catch1,catch2,info1,info2,name,liftUp})=>{
```
that's because we have two children:
```javascript

        <Counters liftUp={catch1} name={name+'-'+name1}/>
        <Counters liftUp={catch2} name={name+'-'+name2}/>
```
Pay attention also at the naming, we receive a `name` prop and we define a `name` prop in each children, where `name1` and `name2` are defined in the component:
```javascript

    const name1='counters1'
    const name2='counters2'
```
We as always pass info up with the use of `useEffect` hook and `liftUp` function received as a prop:
```javascript

    useEffect(()=>{
        liftUp.bind(null,{state,dispatch,
        [name1]:info1.current,[name2]:info2.current})()
    },[state,dispatch,info1.current,info2.current])
```
#The `App` component
Finally, we get at the top level component, the `App` component. Here is how it is defined:
```javascript
import React,{createContext,useState,useEffect,useReducer} from 'react'
import * as classes from './index.module.css'
import Counterss from '../Counterss'
import withLiftUp from '../../hocs/withLiftUp'
import {reducer,initialState} from './reducer'

export const StoreContext=createContext()

const App=({catch1,info1})=>{
    const [store,setStore]=useState({})
    const [state,dispatch]=useReducer(reducer,initialState)

    useEffect(()=>{
        setStore({state,dispatch,[name1]:info1.current})
    },[state,dispatch,info1.current])

    const name1='counterss1'

    return (
    <StoreContext.Provider value={store}>
        <div className={classes.general}>
            <Counterss name={name1} liftUp={catch1}/>
        </div>
    </StoreContext.Provider>
    )
}

export default withLiftUp(App)
```
First of all we create a context with `createContext` from `react`:
```javascript

export const StoreContext=createContext()
```
We also create a `store` object and a `setStore` function with the `useState` hook:
```javascript
const [store,setStore]=useState({})
```
and we set it like this in the `useEffect` hook:
```javascript

    useEffect(()=>{
        setStore({state,dispatch,[name1]:info1.current})
    },[state,dispatch,info1.current])
```
`info1` is received as a prop from the use of the *HOC*:
```javascript
const App=({catch1,info1})=>{
```
We also receive `catch1` which is used in here:
```javascript

            <Counterss name={name1} liftUp={catch1}/>
```
and `name1` is defined as follows:
```javascript

    const name1='counterss1'
```
#Conclusion
So that's it, this is how to fully take control of state management in *React* (without *Redux*).
That's the app running:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/ourpo61uiujgwbzn13kr.png)
Try it yourself with a less complex or cumbersome app.