Example:
// `src/state.ts`
import * as E from 'fp-ts/Either'
import * as Eq from 'fp-ts/Eq'
import * as O from 'fp-ts/Option'
import { Dispatch, SetStateAction, useReducer } from 'react'
const isSetStateFn = <A>(s: SetStateAction<A>): s is (a: A) => A => typeof s === 'function'
/**
* `useStable` is roughly analogous to React's `setState`, the difference being
* that `useStable` exposes a way for users to define how React should interpret
* two values when determining whether to trigger a re-render.
*
* The reason this is useful is because internally React uses `Object.is` to compare
* states. While this is a reasonable default given their goal of simplifying their API,
* it can sometimes lead to surprising behavior, and leads to awkward workarounds
* that can lead to code that is difficult to read or debug.
*
* `useStable` makes a different tradeoff, accepting the cost of being explicit upfront
* for more predictable updates/re-rendering at the call site. Users define what it means
* for 2 stateful values to be equivalent (in the form of a binary function), and updates
* don't occur until the function evaluates to false.
*
* @example
* ```typescript
* import * as Eq from "fp-ts/Eq"
* import * as O from "fp-ts/Option"
* import * as N from "fp-ts/number"
* import * as S from "fp-ts/string"
* import { useStable } from "fp-ts-react-stable-hooks"
*
* const initialValue = { id: 1, name: "oof" }
* const eq = Eq.struct({ id: N.Eq, name: S.Eq })
*
* // Now, `data` will only trigger a re-render if its _value_ has changed:
* const [data, setData] = useStable(initialValue, eq)
* ```
*
* @since 1.0.0 (or whatever)
*/
export const useStable = <A>(initState: A, eq: Eq.Eq<A>): [A, Dispatch<SetStateAction<A>>] =>
useReducer(
(s1: A, s2: SetStateAction<A>) => {
const _s2 = isSetStateFn(s2) ? s2(s1) : s2
return eq.equals(s1, _s2) ? s1 : _s2
},
initState
)
Example: