Skip to content

Commit

Permalink
Merge pull request #4 from soywod/develop
Browse files Browse the repository at this point in the history
Release v1.1.0
  • Loading branch information
soywod committed May 27, 2022
2 parents a11cd09 + 1ee710e commit 79da7db
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 35 deletions.
20 changes: 16 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,43 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [1.1.0] - 2022-05-27

### Added

- Motivation and Similar projects section in readme.

### Changed

- The `EffectReducer` takes now a second argument dispatch of type
`React.Dispatch<Action>`.

## [1.0.5] - 2022-05-26

### Fix

- Export only UMD and ESM formats
- Export only UMD and ESM formats.

## [1.0.4] - 2022-05-26

### Fix

- Modern js export
- Fix modern js export.

## [1.0.3] - 2022-05-26

### Fix

- Microbundle export names
- Fix Microbundle export names.

## [1.0.2] - 2022-05-26

## [1.0.1] - 2022-05-26

## [1.0.0] - 2022-05-26

[unreleased]: https://github.com/soywod/react-use-bireducer/compare/v1.0.5...HEAD
[unreleased]: https://github.com/soywod/react-use-bireducer/compare/v1.1.0...HEAD
[1.1.0]: https://github.com/soywod/react-use-bireducer/compare/v1.0.5...v1.1.0
[1.0.5]: https://github.com/soywod/react-use-bireducer/compare/v1.0.4...v1.0.5
[1.0.4]: https://github.com/soywod/react-use-bireducer/compare/v1.0.3...v1.0.4
[1.0.3]: https://github.com/soywod/react-use-bireducer/compare/v1.0.2...v1.0.3
Expand Down
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ The effect reducer just executes effects and can return a cleanup
function. This cleanup function is called when the component unmounts:

```typescript
type EffectReducer<E> = (effect: E) => void | (() => void);
type EffectReducer<E, A> = (effect: E, dispatch: React.Dispatch<A>) => void | (() => void);
```

This pattern helps you to separate state changes from effectful
Expand Down Expand Up @@ -91,6 +91,14 @@ Library](https://testing-library.com/docs/react-testing-library/intro/)
yarn test
```

## Similar projects

- [`useEffectReducer`](https://github.com/davidkpiano/useEffectReducer):
the state reducer exposes a third argument called `exec` to schedule
effects
- [`useElmish`](https://github.com/ncthbrt/react-use-elmish): it is a
mix between `useEffectReducer` and `useBireducer`

## Sponsoring

[![github](https://img.shields.io/badge/-GitHub%20Sponsors-fafbfc?logo=GitHub%20Sponsors&style=flat-square)](https://github.com/sponsors/soywod)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "react-use-bireducer",
"author": "soywod <[email protected]>",
"description": "React hook for managing effects from reducers.",
"version": "1.0.5",
"version": "1.1.0",
"license": "MIT",
"source": "src/index.ts",
"typings": "dist/index.d.ts",
Expand Down
49 changes: 26 additions & 23 deletions src/index.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,18 @@ type State = {
count: number;
};

type Action =
| {type: "increment"; value: number}
| {type: "decrement"; value: number}
| {type: "reset"};
type Action = {type: "update"; value: number} | {type: "reset"};

type Effect = {type: "log"; value: string} | {type: "backup"; count: number};

const stateReducer: StateReducer<State, Action, Effect> = (state, action) => {
switch (action.type) {
case "increment": {
return [
{count: state.count + action.value},
[{type: "log", value: `increment counter +${action.value}`}],
];
}
case "decrement": {
return [
{count: state.count - action.value},
[{type: "log", value: `decrement counter -${action.value}`}],
];
case "update": {
return [{count: action.value}, [{type: "log", value: `set counter ${action.value}`}]];
}
case "reset": {
return [
{count: 0},
state,
[
{type: "log", value: "reset counter"},
{type: "backup", count: state.count},
Expand All @@ -41,14 +29,15 @@ const stateReducer: StateReducer<State, Action, Effect> = (state, action) => {
}
};

const effectReducer: EffectReducer<Effect> = effect => {
const effectReducer: EffectReducer<Effect, Action> = (effect, dispatch) => {
switch (effect.type) {
case "log": {
console.log(effect.value);
return;
}
case "backup": {
localStorage.setItem("backup", String(effect.count));
dispatch({type: "update", value: 0});
return () => {
localStorage.clear();
};
Expand Down Expand Up @@ -80,9 +69,21 @@ describe("useBireducer", () => {
return (
<>
<span data-testid="counter">{state.count}</span>
<button data-testid="decrement" onClick={() => dispatch({type: "decrement", value: 1})} />
<button data-testid="increment" onClick={() => dispatch({type: "increment", value: 1})} />
<button data-testid="reset" onClick={() => dispatch({type: "reset"})} />
<button
data-testid="decrement"
onClick={() => dispatch({type: "update", value: state.count - 1})}
>
decrement
</button>
<button
data-testid="increment"
onClick={() => dispatch({type: "update", value: state.count + 1})}
>
increment
</button>
<button data-testid="reset" onClick={() => dispatch({type: "reset"})}>
reset
</button>
</>
);
}
Expand All @@ -93,15 +94,17 @@ describe("useBireducer", () => {
fireEvent.click(screen.getByTestId("increment"));
fireEvent.click(screen.getByTestId("increment"));
expect(screen.getByTestId("counter")).toHaveTextContent("2");
expect(console.log).toHaveBeenNthCalledWith(2, "increment counter +1");
expect(console.log).toHaveBeenNthCalledWith(1, "set counter 1");
expect(console.log).toHaveBeenNthCalledWith(2, "set counter 2");

fireEvent.click(screen.getByTestId("decrement"));
expect(screen.getByTestId("counter")).toHaveTextContent("1");
expect(console.log).toHaveBeenLastCalledWith("decrement counter -1");
expect(console.log).toHaveBeenNthCalledWith(3, "set counter 1");

fireEvent.click(screen.getByTestId("reset"));
expect(screen.getByTestId("counter")).toHaveTextContent("0");
expect(console.log).toHaveBeenLastCalledWith("reset counter");
expect(console.log).toHaveBeenNthCalledWith(4, "reset counter");
expect(console.log).toHaveBeenNthCalledWith(5, "set counter 0");
expect(localStorage.setItem).toHaveBeenNthCalledWith(1, "backup", "1");
expect(localStorage.clear).not.toHaveBeenCalled();

Expand Down
14 changes: 8 additions & 6 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import {useCallback, useEffect, useReducer, useRef, useState} from "react";
import {Dispatch, useCallback, useEffect, useReducer, useRef, useState} from "react";

export type StateReducer<S, A, E> = (state: S, action: A) => [S, E[]];

export type EffectReducer<E> = (effect: E) => EffectCleanup | void;
export type EffectReducer<E, A> = (effect: E, dispatch: Dispatch<A>) => EffectCleanup | void;
export type EffectCleanup = () => void;

export function useBireducer<S, A, E>(
stateReducer: StateReducer<S, A, E>,
effectReducer: EffectReducer<E>,
effectReducer: EffectReducer<E, A>,
defaultState: S,
) {
): [S, Dispatch<A>] {
const [effects, setEffects] = useState<E[]>([]);
const cleanups = useRef<EffectCleanup[]>([]);

Expand All @@ -22,10 +22,12 @@ export function useBireducer<S, A, E>(
[stateReducer],
);

const [state, dispatch] = useReducer(reducer, defaultState);

useEffect(() => {
const effect = effects.pop();
if (effect) {
const cleanup = effectReducer(effect);
const cleanup = effectReducer(effect, dispatch);
if (cleanup) cleanups.current.push(cleanup);
setEffects([...effects]);
}
Expand All @@ -39,7 +41,7 @@ export function useBireducer<S, A, E>(
};
}, []);

return useReducer(reducer, defaultState);
return [state, dispatch];
}

export default {useBireducer};

0 comments on commit 79da7db

Please sign in to comment.