Why the wheel

Hook has its own wheel halo

I won’t say much about React Hook. Hooks provide the ability to abstract state, which naturally leads to the idea of extracting global state based on hooks. It comes with its own wheel halo, so there are a lot of hook-based state management tools in the community, such as icestore and Stamen, which were launched by the Flying Ice team a few days ago. However, I prefer unstated-next.

So why build your own wheels when everyone else has built so many? Naturally because:

Other people don’t have enough wheels

For example, unstated-next is essentially globalizing a custom hook. Good idea, but a little too granular. State, actions, and Effects must be maintained in a custom hook. A series of internal actions, effects need to add useCallback, useMemo is also a bit of a hassle, if you pull out of the external, you need to pass a lot of parameters, write TS, you need to write a lot of generics. In short, if the project is relatively complex, it will be tiring to write.

Stamen is not bad either. Declare a store containing state, Reducer, and Effects. And there is no need to package the component Provider, each place at will plug, respond to the update. I don’t like dispatch very much. I can’t locate the action or effect declaration directly and lose the input and output parameter types.

Icestore’s problems are similar. Said to support TS, in fact, is incomplete, look at the source code, the type is completely lost. I’m also not a big fan of namespaces.

Of course, these problems can also be optimized. But why? I didn’t have a few lines of code. I wrote the wheel myself before I had time for PR. So in short, build your own.

My ideal type

What do I want my own state management tool to be? Before HOOx, I actually implemented a version that basically copied a version of DVA’s API (replacing yield with async/await). It’s kind of like icestore, except without the namespace. The most deadly and unsolvable problem is missing types, missing function references.

Then I summed up what I really wanted:

  1. Global status management, and not a single store;
  2. Actions and Effects are normal functions, declared separately and referred to directly;
  3. Perfect TS support.

So the goal was very simple, it could be said to be the unstated-next unhook package version. So I implemented a version that looked like this:

HooX

Creating global State

// store.js
import createHoox from 'hooxjs'

// Declare the global initial state
const state = {
  count: 1
}

/ / create a store
export const { setHoox, getHoox, useHoox } = createHoox(state)

// Create an action
export const up = (a)= > setHoox(({ count }) = > ({ count: count + 1 }))

// Create an effect
export const effectUp = (a)= > {
  const [{ count }, setHoox] = getHoox();
  const newState = { count: count + 1 }
  return fetch('/api/up', newState).then((a)= > setHoox(newState))
  // Or refer directly to action
  // return fetch('/api/up', newState).then(up)
}
Copy the code

Consumption status

import { useHoox, up, effectUp } from './store';

function Counter() {
  const [state] = useHoox()
  return (
    <div>
      <div>{state.count}</div>
      <button onClick={up}>up</button>
      <button onClick={effectUp}>effectUp</button>
    </div>)}Copy the code

Modify state directly

import { useHoox } from './store';

function Counter() {
  const [state, setHoox] = useHoox()
  return (
    <div>
      <div>{state.count}</div>
      <input
        value={state.count}
        onChange={value= > setHoox({ count: value })}
      /
    </div>)}Copy the code

Reset the state

We know that in the class component, state merges are done through this.setState. But in the function component, setState, the second argument returned by useState, is again a replacement update. In practice, we all have appeals. Especially for non-TS projects, the state model may be dynamic and may need to be reset. To satisfy everyone’s needs, I’ve also added an API for everyone to use

import { useHoox } from './store';

function Counter() {
  const [state, setHoox, resetHoox] = useHoox()
  return (
    <div>
      {state ? <div>{state.count}</div> : null}
      <button onClick={()= > resetHoox(null)>reset</button>
    </div>)}Copy the code

Global computed

Using the API above, we can actually achieve a similar effect to computed in VUE.

import { useHoox } from './store';

export function useDoubleCount () {
  const [{ count }] = useHoox();
  return count * 2
}
Copy the code

For very complex operations, we can also use react useMemo for optimization.

import { useHoox } from './store';

export function usePowCount (number = 2) {
  const [{ count }] = useHoox();
  return useMemo((a)= > Math.pow(count, number), [count, number])
}
Copy the code

In addition, some global effects can also be implemented.

Not pretty enough

To the Provider

Hoox is based on the context and useState implementations. Since state is stored in context, like Redux, components that consume state must be descendants of the corresponding context.provider. Such as:

import { Provider } from './store';
import Counter from './counter';

function App() {
  return <Provider>
    <Counter />
  </Provider>
}
Copy the code

As a result, if a component consumes two stores, it needs to be a descendant of both providers.

Hoox provides a syntactic sugar createContainer that simplifies the syntax slightly.

import { createContainer } from './store';
import Counter from './counter';

function App () {
  return <Counter />
}
  
export default createContainer(App)
Copy the code

Other bad things

Leave it to the comments

Github

Specific source code and API introduction can be seen at github: github.com/wuomzfx/hoo…

I will not elaborate on the source code part, there are not a few lines of code, you can see.