@pacote/flux-actions

Typed actions and reducers for Flux and Flux-like architectures.

Usage no npm install needed!

<script type="module">
  import pacoteFluxActions from 'https://cdn.skypack.dev/@pacote/flux-actions';
</script>

README

@pacote/flux-actions

Redux Demo React Hooks API Demo version minified minified + gzip

Typed actions and reducers for Flux and Flux-like architectures, including the useReducer React hook.

Demos

Installation

yarn add @pacote/flux-actions

Usage

createAction<Payload>(type: string)

Action payloads

import { createAction } from '@pacote/flux-actions'
const changeYear = createAction<number>('CHANGE_YEAR')

Calling changeYear(1955) will generate the following action object:

{
  type: 'CHANGE_YEAR',
  payload: 1955
}

Action metadata

The action creator supports an optional metadata parameter. For example, changeYear(1955, meta: { test: true }) will create:

{
  type: 'CHANGE_YEAR',
  payload: 1955,
  meta: {
    test: true
  }
}

Actions with errors

Unlike Flux Standard Actions, the action creator does not handle errors. Instead, consider using monadic objects like Either to wrap error conditions:

import { createAction } from '@pacote/flux-actions'
import { Either, tryCatch } from 'fp-ts/lib/Either'

const changeYear = createAction<Either<Error, number>>('CHANGE_YEAR')

changeYear(tryCatch(...))

isType<Payload>(creator: ActionCreator<Payload>, action: Action<Payload>)

Checks whether an action matches the provided type. This ensures the action is properly typed inside the guard block.

import { createAction, isType } from '@pacote/flux-actions'

const changeYear = createAction<number>('CHANGE_YEAR')
const action = changeYear(1985)

if (isType(changeYear, action)) {
  // action.payload is a number inside the guard
  console.log(action.payload)
}

reduceFromState(initialState: State)

Creates a reducer which matches action handlers to appropriate types.

import { createAction, reduceFromState } from '@pacote/flux-actions'

const person = createAction<{ name: string }>('PERSON')
const dog = createAction<{ name: string }>('DOG')
const car = createAction<{ brand: string }>('CAR')

const reducer = reducerFromState({ now: 'None', then: '' })
  // Matches multiple actions:
  .on([person, dog], (s, a) => ({ now: a.payload.name, then: s.now }))
  // Matches a single action:
  .on(car, (s, a) => ({ now: a.payload.brand, then: s.now }))

const s2 = reducer(undefined, person({ name: 'Marty McFly' }))
// { now: 'Marty McFly', then: 'None' })

const s3 = reducer(s2, dog({ name: 'Einstein' }))
// { now: 'Einstein', then: 'Marty McFly' })

const s4 = reducer(s3, car({ brand: 'DeLorean' }))
// { now: 'DeLorean', then: 'Einstein' })

Reducing actions with errors wrapped in Either could look something like this:

import { createAction, reduceFromState } from '@pacote/flux-actions'
import { Either, tryCatch } from 'fp-ts/lib/Either'

type State = {
  year: number
  error?: Error
}

const changeYear = createAction<Either<Error, number>>('CHANGE_YEAR')

const reducer = reducerFromState<State>({ year: 1985 })
  .on(changeYear, (state, { payload } ) => payload.fold<State>(
    error => ({ ...state, error }),
    year => ({ year, error: undefined })
  ))

reducer(undefined, changeYear(tryCatch(...)))

License

MIT © Luís Rodrigues.