Skip to content
This repository has been archived by the owner on Jan 7, 2025. It is now read-only.

Release v1.1.0 #4

Merged
merged 4 commits into from
May 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 <clement.douin@posteo.net>",
"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};