0.4.1 • Published 5 years ago

@heliopolis/hooks v0.4.1

Weekly downloads
7
License
MIT
Repository
gitlab
Last release
5 years ago

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.

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.

// 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.

// 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
0.4.1

5 years ago

0.4.0

5 years ago

0.3.0

5 years ago

0.2.1

5 years ago

0.2.3

5 years ago

0.2.2

5 years ago

0.2.0

5 years ago