# use-reducer-async

[![CI](https://img.shields.io/github/workflow/status/dai-shi/use-reducer-async/CI)](https://github.com/dai-shi/use-reducer-async/actions?query=workflow%3ACI)
[![npm](https://img.shields.io/npm/v/use-reducer-async)](https://www.npmjs.com/package/use-reducer-async)
[![size](https://img.shields.io/bundlephobia/minzip/use-reducer-async)](https://bundlephobia.com/result?p=use-reducer-async)
[![discord](https://img.shields.io/discord/627656437971288081)](https://discord.gg/MrQdmzd)

React useReducer with async actions

## Introduction

React useReducer doesn't support async actions natively.
Unlike Redux, there's no middleware interface, but hooks are composable.

This is a tiny library to extend useReducer's dispatch
so that dispatching async actions invoke async functions.

## Install

```bash
npm install use-reducer-async
```

## Usage

```javascript

import { useReducerAsync } from "use-reducer-async";

const initialState = {
  sleeping: false,
};

const reducer = (state, action) => {
  switch (action.type) {
    case 'START_SLEEP': return { ...state, sleeping: true };
    case 'END_SLEEP': return { ...state, sleeping: false };
    default: throw new Error('no such action type');
  }
};

const asyncActionHandlers = {
  SLEEP: ({ dispatch }) => async (action) => {
    dispatch({ type: 'START_SLEEP' });
    await new Promise(r => setTimeout(r, action.ms));
    dispatch({ type: 'END_SLEEP' });
  },
};

const Component = () => {
  const [state, dispatch] = useReducerAsync(reducer, initialState, asyncActionHandlers);
  return (
    <div>
      <span>{state.sleeping ? 'Sleeping' : 'Idle'}</span>
      <button type="button" onClick={() => dispatch({ type: 'SLEEP', ms: 1000 })}>Click</button>
    </div>
  );
};
```

### Notes for abortability

All async action handlers receive `signal` in the argument.
Refer [`examples/04_abort/src`](./examples/04\_abort/src) for the usage.

Note: The implementation depends on [AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) in the DOM spec.
If you are using an environment that doesn't have AbortController (for example IE11), you need a polyfill:
[1](https://github.com/mo/abortcontroller-polyfill)
[2](https://github.com/mysticatea/abort-controller)

## API

<!-- Generated by documentation.js. Update this documentation by updating the source code. -->

### useReducerAsync

useReducer with async actions

#### Parameters

*   `reducer` **R** 
*   `initialState` **ReducerState\<R>** 
*   `asyncActionHandlers` **AsyncActionHandlers\<R, AsyncAction>** 

#### Examples

```javascript
import { useReducerAsync } from 'use-reducer-async';

const asyncActionHandlers = {
  SLEEP: ({ dispatch, getState, signal }) => async (action) => {
    dispatch({ type: 'START_SLEEP' });
    await new Promise(r => setTimeout(r, action.ms));
    dispatch({ type: 'END_SLEEP' });
  },
  FETCH: ({ dispatch, getState, signal }) => async (action) => {
    dispatch({ type: 'START_FETCH' });
    try {
      const response = await fetch(action.url);
      const data = await response.json();
      dispatch({ type: 'FINISH_FETCH', data });
    } catch (error) {
      dispatch({ type: 'ERROR_FETCH', error });
    }
  },
};
const [state, dispatch] = useReducerAsync(reducer, initialState, asyncActionHandlers);
```

Returns **\[ReducerState\<R>, Dispatch\<ExportAction>]** 

## Examples

The [examples](examples) folder contains working examples.
You can run one of them with

```bash
PORT=8080 npm run examples:01_minimal
```

and open <http://localhost:8080> in your web browser.

You can also try them in codesandbox.io:
[01](https://codesandbox.io/s/github/dai-shi/use-reducer-async/tree/main/examples/01\_minimal)
[02](https://codesandbox.io/s/github/dai-shi/use-reducer-async/tree/main/examples/02\_typescript)
[03](https://codesandbox.io/s/github/dai-shi/use-reducer-async/tree/main/examples/03\_getstate)
[04](https://codesandbox.io/s/github/dai-shi/use-reducer-async/tree/main/examples/04\_abort)
