Skip to content

Commit f490deb

Browse files
committed
Add SetStateAction support to useStable
1 parent 5c62227 commit f490deb

File tree

4 files changed

+44
-8
lines changed

4 files changed

+44
-8
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
By default React will perform a JavaScript object reference comparison of two objects, otherwise known as shallow object comparison, which results in extra re-renders on “unchanged” values for fp-ts data types.
99

10-
For example: Take an fp-ts type such as `Option` who’s underlying data structure is is `{_tag: "Some", value: 1}`. Compared with another `Option` who's value is also `{_tag: "Some", value: 1}`, they will be considered different objects with JavaScript object reference comparison since `O.some(1) !== O.some(1)`.
10+
For example: Take an fp-ts type such as `Option` who’s underlying data structure is is `{_tag: "Some", value: 1}`. Compared with another `Option` who's value is also `{_tag: "Some", value: 1}`, they will be considered different objects with JavaScript object reference comparison since `O.some(1) !== O.some(1)`.
1111

1212
However, an equality function can dive down into the underlying `value` type and prove its equality. Given that, an equality function such as `O.getEq(Eq.eqNumber)` can prove that `O.getEq(Eq.eqNumber).equals(O.some(1), O.some(1)) === true`. Using these stable hooks instead of the basic react hooks will result in fewer unnecessary re-renders when using fp-ts data types.
1313

@@ -59,7 +59,7 @@ useStableEffect(() => {
5959

6060
## API
6161

62-
| React Hook | Stable Hook | Description |
62+
| React Hook | Stable Hook | Description |
6363
|-------------|-------------------|-------------|
6464
| useState | useStable | Base hook that requires an equality function |
6565
| | useStableE | Helper function which automatically proves the top level equality function for `Either` |

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "fp-ts-react-stable-hooks",
3-
"version": "1.1.0",
3+
"version": "1.2.0",
44
"description": "Stable hooks for react using FP-TS equality checks instead of shallow (reference) object comparison",
55
"main": "dist/lib/index.js",
66
"module": "dist/es2015/index.js",

src/state.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
11
import * as E from 'fp-ts/Either';
22
import * as Eq from 'fp-ts/Eq';
33
import * as O from 'fp-ts/Option';
4-
import { Dispatch, useReducer } from 'react';
4+
import { Dispatch, SetStateAction, useReducer } from 'react';
55

6-
export const useStable = <A>(initState: A, eq: Eq.Eq<A>): [A, Dispatch<A>] =>
6+
const isSetStateFn = <A>(s: SetStateAction<A>): s is (a: A) => A => typeof s === 'function';
7+
8+
export const useStable = <A>(initState: A, eq: Eq.Eq<A>): [A, Dispatch<SetStateAction<A>>] =>
79
useReducer(
8-
(s1: A, s2: A) => eq.equals(s1, s2) ? s1 : s2,
10+
(s1: A, s2: SetStateAction<A>) => {
11+
const _s2 = isSetStateFn(s2) ? s2(s1) : s2;
12+
return eq.equals(s1, _s2) ? s1 : _s2;
13+
},
914
initState
1015
);
1116

12-
export const useStableO = <A>(initState: O.Option<A>): [O.Option<A>, Dispatch<O.Option<A>>] =>
17+
export const useStableO = <A>(initState: O.Option<A>): [O.Option<A>, Dispatch<SetStateAction<O.Option<A>>>] =>
1318
useStable(initState, O.getEq(Eq.eqStrict));
1419

15-
export const useStableE = <E, A>(initState: E.Either<E, A>): [E.Either<E, A>, Dispatch<E.Either<E, A>>] =>
20+
export const useStableE = <E, A>(initState: E.Either<E, A>): [E.Either<E, A>, Dispatch<SetStateAction<E.Either<E, A>>>] =>
1621
useStable(initState, E.getEq(Eq.eqStrict, Eq.eqStrict));

test/state.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,5 +58,36 @@ tests.forEach((t: Test) => {
5858

5959
expect(result.current[0]).toStrictEqual(t.val2);
6060
});
61+
62+
test('should return the same value with an identity SetStateAction fn', () => {
63+
const { result } = renderHook(() => t.fn(t.val1a));
64+
65+
act(() => {
66+
result.current[1]((v: any) => v);
67+
});
68+
69+
expect(result.current[0]).toStrictEqual(t.val1a);
70+
});
71+
72+
test('should not update the state if the same value is returned from a SetStateAction fn', () => {
73+
const { result } = renderHook(() => t.fn(t.val1a));
74+
75+
act(() => {
76+
result.current[1](() => t.val1b);
77+
});
78+
79+
expect(result.current[0]).toStrictEqual(t.val1a);
80+
});
81+
82+
83+
test('should update the state if a different value is returned from a SetStateAction fn', () => {
84+
const { result } = renderHook(() => t.fn(t.val1a));
85+
86+
act(() => {
87+
result.current[1](() => t.val2);
88+
});
89+
90+
expect(result.current[0]).toStrictEqual(t.val2);
91+
});
6192
});
6293
});

0 commit comments

Comments
 (0)