Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/elderfo/use-dispatch-action


https://github.com/elderfo/use-dispatch-action

Last synced: 21 days ago
JSON representation

Awesome Lists containing this project

README

        

# use-dispatch-action

Typed utilities for improving the experience with `useReducer`.

![npm](https://img.shields.io/npm/v/use-dispatch-action)
![GitHub Workflow Status](https://img.shields.io/github/workflow/status/elderfo/use-dispatch-action/CI)
![node-current](https://img.shields.io/node/v/use-dispatch-action)
![npm bundle size](https://img.shields.io/bundlephobia/min/use-dispatch-action)
![NPM](https://img.shields.io/npm/l/use-dispatch-action)

## Problem

When using `useReducer`

- Dispatching actions are not type safe
- Action creators, while testable, introduce additional boilerplate code
- Changing an action type can lead to a reducer to no longer work properly, if you don't have tests

## Solution

`use-dispatch-action` is a collection of utilities to improve the experience when using the `useReducer` hook.

## Getting started

Install

```bash
yarn add use-dispatch-action
```

Or

```bash
npm install use-dispatch-action
```

## Usage

```typescript
import * as React from 'react';
import { useDispatchAction } from 'use-dispatch-action';

type Actions =
| { type: 'increment' }
| { type: 'decrement' }
| { type: 'addValue'; payload: number };

type State = { counter: number };

const reducer = (state: State, action: Actions): State => {
switch (action.type) {
case 'increment':
return { ...state, counter: state.counter + 1 };
case 'decrement':
return { ...state, counter: state.counter - 1 };
case 'addValue':
return { ...state, counter: state.counter + action.payload };
default:
return state;
}
};

const Component = () => {
const [state, dispatch] = React.useReducer(reducer, { counter: 0 });
const increment = useDispatchAction(dispatch, 'increment');
const decrement = useDispatchAction(dispatch, 'decrement');
const addValue = useDispatchAction(dispatch, 'addValue');

return (


{state.counter}

Increment
Decrement
addValue(2)}>Add Two

);
};
```

# API

## type `Action`

A utilty type for defining actions

```typescript
type Action = {
type: TActionType;
payload?: TPayload;
};
```

### Example

```typescript
type Actions = Action<'incrementOne'> | Action<'increment', number>;
```

## `useDispatchAction`

Creates type safe dispatch functions for the specified action

```typescript
useDispatchAction(
dispatch: React.Dispatch,
action: string
) : DispatchAction
```

### Arguments

- `dispatch: React.Dispatch` - A dispatch method retured from `React.useReducer`
- `action: string` - The type of the action

### Returns

- `DispatchAction` - Function to dispatch action

```typescript
// For actions without a payload
() => void;
// For actions with a payload
(payload: TPayload) => void;
```

### Example ([types/reducer](#types-and-reducer-for-examples))

```typescript
const Component = () => {
const [state, dispatch] = React.useReducer(reducer, { counter: 0 });
const increment = useDispatchAction(dispatch, 'increment');
const decrement = useDispatchAction(dispatch, 'decrement');
const addValue = useDispatchAction(dispatch, 'addValue');

return (


{state.counter}

increment()}>Increment
decrement()}>Decrement
addValue(2)}>Add Two

);
};
```

## `useDispatchReducer`

Creates a reducer with a type safe dispatch method

```typescript
useDispatchReducer (
reducer: React.Reducer,
initialState: TState
) : [state: TState, ActionDispatcher]
```

`TState` and `TAction` can be infered by providing the type of a reducer.

```typescript
useDispatchReducer(
reducer: TReducer,
initialState: TState
) : [state: TState, ActionDispatcher]
```

### Arguments

- `reducer: React.Reducer` - The reducer
- `initialState: TState` - State to initialize the reducer with. A note, `useDispatchReducer` does not implement lazy loading the state

### Returns

A tuple with:

- `state: TState` - State of the reducer
- `ActionDispatcher` - Function to dispatch actions in the form of tuples
```typescript
// For actions without a payload
([type: string]) => void;
// For actions with a payload
([type: string, payload: TPayload]) => void;
```

### Examples ([types/reducer](#types-and-reducer-for-examples))

With type inference

```typescript
const Component = () => {
const [state, dispatch] = useDispatchReducer(reducer, { counter: 0 });
const increment = () => dispatch(['increment']);
const decrement = () => dispatch(['decrement']);
const addValue = (number: number) => dispatch(['addValue', number]);

return (


{state.counter}

Increment
Decrement
addValue(2)}>Add Two

);
};
```

With known State and Action types

```typescript
const Component = () => {
const [state, dispatch] = useDispatchReducer(reducer, {
counter: 0,
});
const increment = () => dispatch(['increment']);
const decrement = () => dispatch(['decrement']);
const addValue = (number: number) => dispatch(['addValue', number]);

return (


{state.counter}

Increment
Decrement
addValue(2)}>Add Two

);
};
```

Only know the State type? The Action type can be inferred from the reducer as long as the actions are typed.

```typescript
const Component = () => {
const [state, dispatch] = useDispatchReducer(reducer, { counter: 0 });
const increment = () => dispatch(['increment']);
const decrement = () => dispatch(['decrement']);
const addValue = (number: number) => dispatch(['addValue', number]);

return (


{state.counter}

Increment
Decrement
addValue(2)}>Add Two

);
};
```

## DispatchContext

A context based dispatcher used to prevent prop drilling

```typescript
export type DispatchContextProps = {
initialState: TState;
reducer: React.Reducer;
};
```

### props

- `initialState: TState` - state to initialize the reducer with
- `reducer: React.Reducer` - The reducer

### Examples ([types/reducer](#types-and-reducer-for-examples))

Using a consumer

```typescript
const DispatchContext = () => {
return (


{({ state, dispatch }: DispatchProps) => (


{state.counter}

dispatch(['increment'])}>Increment
dispatch(['decrement'])}>Decrement
dispatch(['addValue', 2])}>Add Two

)}


);
};
```

Using `useDispatchContext`

```typescript
const Component = () => {
return (



);
};

const Counter = () => {
const [state, dispatch] = useDispatchContext();

return (


{state.counter}

dispatch(['increment'])}>Increment
dispatch(['decrement'])}>Decrement
dispatch(['addValue', 2])}>Add Two

);
};
```

## Types and reducer for examples

```typescript
type Actions =
| { type: 'increment' }
| { type: 'decrement' }
| { type: 'addValue'; payload: number };

type State = { counter: number };

const reducer = (state: State, action: Actions): State => {
switch (action.type) {
case 'increment':
return { ...state, counter: state.counter + 1 };
case 'decrement':
return { ...state, counter: state.counter - 1 };
case 'addValue':
return { ...state, counter: state.counter + action.payload };
default:
return state;
}
};
```