strong-reducer
TypeScript icon, indicating that this package has built-in type declarations

0.4.5 • Public • Published

strong-reducer

A useReducer hook for React that's easy to use with Typescript.

Important Concurrency Note:

strong-reducer supports synchronous, async (Promise), and async iterable (async function*) reducers. However, only one dispatch can be active at a time, this prevents race conditions between dispatches. If you send a dispatch while another dispatch is being awaited, then that previous dispatch will be aborted.

What does this mean for...

  • synchronous reducers: a synchronous reducer cannot be canceled because there's no chance of concurrency. However, it will abort a running asynchronous reducer.
  • async (Promise) based reducers: the return value will be discarded if aborted.
  • async iterable (async function*) based reducers: the yielded values will be discarded if aborted.
type States = "idle" | "submitting" | "success" | "error";

const [state, { submit }] = useStrongReducer<States>("idle")({
  submit: (value: string) =>
    async function* () {
      yield "submitting";
      try {
        await apiClient.submitValue(value);
        yield "success";
      } catch (err) {
        yield "error";
      }
      await sleep(700);
      yield "idle";
    },
});

// click this a few times,
// it will not revert to idle 700ms after the FIRST click if there were subsequent clicks
// it will only revert back to idle 700ms after the most recent click
return (
  <button onClick={() => submit(someValue)}>
    {
      {
        idle: "Submit",
        submitting: "Processing...",
        success: "Succeeded!",
        error: "Error!",
      }[state]
    }
  </button>
);

Install

npm i strong-reducer

or

yarn add strong-reducer

Use

const [countBy, setCountBy] = React.useState(5);

const [count, useDispatcher] = useStrongReducerWithProps(
  0, // <------------------------------------- initial state
  countBy // <-------------------------------- reducer props
)({
  increase: // <------------------------------ dispatcher method name
    () =>  // <------------------------------- dispatcher method args (none)
      countBy => // <------------------------- reducer props
        count => count + countBy, // <-------- state updater
  
  set: // <----------------------------------- dispatcher method name
    (newCount: number) => // <---------------- dispatcher method args
      () => // <------------------------------ reducer props (omitted)
        newCount, // <------------------------ new state
  
  setAsync: // <------------------------------ dispatcher method name
    (newCount: number) => // <---------------- dispatcher method args
      () =>   // <---------------------------- reducer props (omitted)
        Promise.resolve(newCount), // <------- new state (async)
  
  increaseAsync: // <------------------------- dispatcher method name
    () => // <-------------------------------- dispatcher method args (none)
      countBy =>   // <----------------------- reducer props
        Promise.resolve( // <-------------------------------------------
          oldCount => oldCount + countBy // <- new state (async, updater)
        ), // <---------------------------------------------------------
  
  reload: // <-------------------------------- dispatcher method name
    () => // <-------------------------------- dispatcher method args (none)
      async () =>   // <---------------------- reducer props (omitted)
        await fetchFromServer(), // <--------- async fetch
  
  setAsyncIter: // <-------------------------- dispatcher method name
    () => // <-------------------------------- dispatcher method args
      async function* (
        props,
        dispatch // <------------------------- dispatch: { props, state, abort }
      ) {
        yield 1; // <------------------------- new state (async)
        await sleep(500);
        yield 2; // <------------------------- new state (async)
        await sleep(500);
        yield 3; // <------------------------- new state (async)
      },
});

return (
  <>
    <div>{ count }</div>
    <input type="number" onChange={ev => setCountBy(Number(ev.target.value))} />
    <button onClick={dispatcher.increase}>Increase</button>
    <button onClick={() => dispatcher.set(10)}>Set to 10</button>
    <button onClick={() => dispatcher.setAsync(10)}>Set to 10 async</button>
    <button onClick={dispatcher.increaseAsync}>Increase Async</button>
  </>
);

Package Sidebar

Install

npm i strong-reducer

Weekly Downloads

29

Version

0.4.5

License

MIT

Unpacked Size

106 kB

Total Files

17

Last publish

Collaborators

  • joeflateau