npm package: @heliopolis/hooks
Description: A utility library with a few convenient hooks for React / Mobx
Author: Nick
Latest version: 0.4.1

# Modular hooks

This little utility library provides a few helpful hooks and hook factories for working with function components in React >=16.9.

## Actors

State can be managed in a safe way with 2 helpful functions:
- `useActor(caseReducers, initialState)`
- `createActorHook(stateFactory, caseReducers)`

Given both functions require some state to be defined and some case reducers to act upon said state, let's define a function that initializes state and case reducers to act on this state.

```ts
interface Person {
  name: string
  age: number
}

function createPerson(name?: Person['name']): Person {
  return {
    name: name ?? 'Unnamed person',
    age: 40
  }
}

const reducers = {
  incrementAge(person: Person): Person {
    return {
      ...person,
      age: person.age + 1
    }
  },
  rename(person: Person, newName: Person['name']): Person {
    return {
      ...person,
      name: newName
    }
  }
}
```

> Note: the inspiration for the **Actor** name is due to the similarities between the redux/_reducer_ approach to state management and the Actor model.  Namely, state is mutated internally by the actor (in this case, reducer) and changes to said state are effectuated by sending messages to the actor (actions to the reducer).  Changes are handled linearly in the order in which they are received.

### `useActor`

Designed for a reducer/redux-like experience, `useActor` is a convenience hook around `useReducer` that, given case reducers and an initial state, returns a state (like `useReducer`) and a set of type-safe dispatchers for each of the case reducers.

```tsx
// Using `Person`, `createPerson` and `reducers` from the previous example

const initialPerson = createPerson('Sarah Kerrigan')

function PersonCard() {
  const [person, dispatchers] = useActor(reducers, initialPerson)

  return <div>
    <h1>Name: {person.name}</h1>
    {/* Text input whose `onEnter` callback is called when pressing ENTER */}
    <TextInput onEnter={dispatchers.rename} />
  </div>
}

// De-structuring is also supported since the hook returns plain JS objects

function PersonCard() {
  const [{ name }, { incrementAge, rename }] = useActor(reducers, initialPerson)

  // incrementAge: () => void
  // rename: (newName: string) => void

  return <div>
    <h1>Name: {name}</h1>
    <TextInput onEnter={rename}>
    <button onClick={incrementAge}>Age by 1 year</button>
  </div>
}
```

### `createActorHook`

While `useActor` may be convenient, we can make this interface even more convenient by removing the boilerplate of providing the case `reducers` with every call to `useActors`.  This is where `createActorHook` comes in.

Given case reducers and a factory that returns state, `createActorHook` will return a hook with the same signature as the provided factory.  This hook when used will return a tuple of `[state, dispatchers]`, precisely like `useActor` above while keeping component logic very readable.

```tsx
// Using the `Person`, `createPerson` and `reducers` in the first example

const usePerson = createActorHook(createPerson, reducers)
// usePerson: (name?: string) => [Person, Dispatchers]

function PersonCard() {
  const [{ name }, { rename }] = usePerson('Zeratul')

  return <div>
    <h1>Name: {name}</h1>
    <TextInput onChange={rename} />
  </div>
}
```

The main benefits we see here are:
- No need to provide `reducers` explicitly with every hook use
- No mention of _actors_ in the component which may allows us to stick to the language of our business domain
- Customizable hook arguments signature since it inherits its arguments signature from the provided factory