Skip to content

Commit

Permalink
Add SetStateAction support to useStable
Browse files Browse the repository at this point in the history
  • Loading branch information
jleider committed Mar 29, 2021
1 parent 5c62227 commit f490deb
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 8 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

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.

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)`.
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)`.

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.

Expand Down Expand Up @@ -59,7 +59,7 @@ useStableEffect(() => {

## API

| React Hook | Stable Hook | Description |
| React Hook | Stable Hook | Description |
|-------------|-------------------|-------------|
| useState | useStable | Base hook that requires an equality function |
| | useStableE | Helper function which automatically proves the top level equality function for `Either` |
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "fp-ts-react-stable-hooks",
"version": "1.1.0",
"version": "1.2.0",
"description": "Stable hooks for react using FP-TS equality checks instead of shallow (reference) object comparison",
"main": "dist/lib/index.js",
"module": "dist/es2015/index.js",
Expand Down
15 changes: 10 additions & 5 deletions src/state.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
import * as E from 'fp-ts/Either';
import * as Eq from 'fp-ts/Eq';
import * as O from 'fp-ts/Option';
import { Dispatch, useReducer } from 'react';
import { Dispatch, SetStateAction, useReducer } from 'react';

export const useStable = <A>(initState: A, eq: Eq.Eq<A>): [A, Dispatch<A>] =>
const isSetStateFn = <A>(s: SetStateAction<A>): s is (a: A) => A => typeof s === 'function';

export const useStable = <A>(initState: A, eq: Eq.Eq<A>): [A, Dispatch<SetStateAction<A>>] =>
useReducer(
(s1: A, s2: A) => eq.equals(s1, s2) ? s1 : s2,
(s1: A, s2: SetStateAction<A>) => {
const _s2 = isSetStateFn(s2) ? s2(s1) : s2;
return eq.equals(s1, _s2) ? s1 : _s2;
},
initState
);

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

export const useStableE = <E, A>(initState: E.Either<E, A>): [E.Either<E, A>, Dispatch<E.Either<E, A>>] =>
export const useStableE = <E, A>(initState: E.Either<E, A>): [E.Either<E, A>, Dispatch<SetStateAction<E.Either<E, A>>>] =>
useStable(initState, E.getEq(Eq.eqStrict, Eq.eqStrict));
31 changes: 31 additions & 0 deletions test/state.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,36 @@ tests.forEach((t: Test) => {

expect(result.current[0]).toStrictEqual(t.val2);
});

test('should return the same value with an identity SetStateAction fn', () => {
const { result } = renderHook(() => t.fn(t.val1a));

act(() => {
result.current[1]((v: any) => v);
});

expect(result.current[0]).toStrictEqual(t.val1a);
});

test('should not update the state if the same value is returned from a SetStateAction fn', () => {
const { result } = renderHook(() => t.fn(t.val1a));

act(() => {
result.current[1](() => t.val1b);
});

expect(result.current[0]).toStrictEqual(t.val1a);
});


test('should update the state if a different value is returned from a SetStateAction fn', () => {
const { result } = renderHook(() => t.fn(t.val1a));

act(() => {
result.current[1](() => t.val2);
});

expect(result.current[0]).toStrictEqual(t.val2);
});
});
});

0 comments on commit f490deb

Please sign in to comment.