Full State Management in Vue 3 (without Vuex)
Tags: vue
Published: 2020-09-20 21:16:10

This post is a brother of [Full State Management in React (without Redux)](https://dev.to/roggc/full-state-management-in-react-without-redux-50ed). 

The thing is we define a local state for each component, through the use of `useReducer` hook, then we mount up a tree of all the local states of all the components instantiated, and then make available that *store* through the use of `useContext` in *React* and `provide`-`inject` api in *Vue*.

Because in *Vue* we don't have a `useReducer` hook, we must do a simple equivalent one.

In this way we achieve a total control of the state in the tree.

#The *useReducer* hook

Let's start by a simple equivalent of the `useReducer` hook in *React*, but for *Vue*. This will be the code:

```javascript
import { reactive } from 'vue'

export const useReducer = (reducer, iState) => {
  const state = reactive(iState)
  const dispatch = (action) => {
    reducer(state, action)
  }
  return [state, dispatch]
}
```

You see it's quite simple. When defining the initial state in a separate file for passing it to the `useReducer` function we must take care to define a function that returns each time (each invocation) a new object representing the initial state. If not, two instances of the same component will end up sharing the same state. Like this:

```javascript
export const iState = () => ({
  counter: 0,
})
```

Then, in the `setup` function of the composition API we do this:

```javascript
  setup(props) {
    const [state, dispatch] = useReducer(reducer, iState())
```

#The `reducer` function

There is a difference in the definition of the *reducer* function respect to the one we do in React. 

This is the *reducer* for this app:

```javascript
export const reducer = (state, action) => {
  switch (action.type) {
    case INCREASE:
      state.counter++
      break
  }
}
```

As you can see we mutate directly the object and don't create a new one because if we did that we will lose reactivity.

#Passing information up to the component tree

The technique used to pass information from down to up is using a *HOC* to provide extra properties to the component, which are `catched` and `infoRef`. 

`catched` is the callback passed to the child from where we want to get (catch) information, and `infoRef` is where we will store that information.

This is the *HOC*:

```javascript
import { ref } from 'vue'

export default (C) => ({
  setup(props) {
    const infoRef1 = ref(null)
    const infoRef2 = ref(null)
    const infoRef3 = ref(null)
    const infoRef4 = ref(null)
    const catched1 = (info) => (infoRef1.value = info)
    const catched2 = (info) => (infoRef2.value = info)
    const catched3 = (info) => (infoRef3.value = info)
    const catched4 = (info) => (infoRef4.value = info)
    return () => {
      return (
        <C
          catched1={catched1}
          catched2={catched2}
          catched3={catched3}
          catched4={catched4}
          infoRef1={infoRef1}
          infoRef2={infoRef2}
          infoRef3={infoRef3}
          infoRef4={infoRef4}
          {...props}
        />
      )
    }
  },
})
```

If you need more `catched` and `infoRef`s you can define them on this *HOC* as many as the maximum number of children a parent will have in the app. 

As you can see we provide to the component with extra properties `catched1`, `catched2`, etc. The same for `infoRef`.

#How do we use it?

Let's look at the use of it in the component definitions. First, let's stipulate the structure of the app, of the tree. We will have to component definitions, `App` and `Counter`. `App` will instantiate two `Counter`s, while `Counter` does not have any child.

Let's look at the definition of the `App` component:

```javascript
import { provide, reactive, ref, inject } from 'vue'
import Counter from '../Counter'
import styles from './index.module.css'
import withCatched from '../../hocs/withCatched'
import * as counterActions from '../Counter/actions'
import { iState, reducer } from './reducer'
import { useReducer } from '../../hooks/useReducer'

export default withCatched({
  props: ['catched1', 'infoRef1', 'catched2', 'infoRef2'],
  setup(props) {
    const [state, dispatch] = useReducer(reducer, iState)

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

    provide('store', {
      state,
      dispatch,
      [name1]: props.infoRef1,
      [name2]: props.infoRef2,
    })

    const store = inject('store')

    const clicked1 = () => {
      store[name1].value.dispatch(counterActions.increase())
    }

    const clicked2 = () => {
      store[name2].value.dispatch(counterActions.increase())
    }

    return () => {
      return (
        <div className={styles.some}>
          <Counter catched={props.catched1} name={name1} />
          <Counter catched={props.catched2} name={name2} />
          {store[name1].value && store[name1].value.state.counter}
          {store[name2].value && store[name2].value.state.counter}
          <button onClick={clicked1}>increase1</button>
          <button onClick={clicked2}>increase2</button>
        </div>
      )
    }
  },
})
```

You can see how we use named components, that's it, we pass a property `name` to each instance of `Counter` in the `App` component.

Now, let's look at the definition of the `Counter` component:

```javascript
import { onMounted, reactive, ref, inject, onUpdated } from 'vue'
import styles from './index.module.css'
import { useReducer } from '../../hooks/useReducer'
import { reducer, iState } from './reducer'

export default {
  props: ['catched', 'name'],
  setup(props) {
    const [state, dispatch] = useReducer(reducer, iState())

    onMounted(() => {
      props.catched.bind(null, { state, dispatch })()
    })

    const store = inject('store')

    return () => {
      return (
        <div class={styles.general}>
          {store[props.name].value && store[props.name].value.state.counter}
        </div>
      )
    }
  },
}
```

Pay attention to this:

```javascript
    onMounted(() => {
      props.catched.bind(null, { state, dispatch })()
    })
```

This is how we uplift information to the parent component. In this case, we are sending up `state` and `dispatch`, but we can uplift any information we need to.

#Conclusion

So that's it. This is how we can have perfect control of `state` and `dispatch` of all the components instantiated in the tree.

This is the final result:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/5f8s8an791tf43hm5037.png)

As you can see the two counters are incremented individually.