Replace Atom.setIn() and Atom.reduceIn() with the type-safe Atom.setByPointer() and Atom.reduceByPointer()

This commit is contained in:
Aria Minaei 2022-12-01 17:54:14 +00:00
parent 5b6306bde9
commit ac9d8b4481
15 changed files with 82 additions and 223 deletions

View file

@ -4,10 +4,10 @@ import last from 'lodash-es/last'
import type {Prism} from './prism/Interface'
import {isPrism} from './prism/Interface'
import type {Pointer, PointerType} from './pointer'
import {getPointerParts} from './pointer'
import {isPointer} from './pointer'
import pointer, {getPointerMeta} from './pointer'
import type {$FixMe, $IntentionalAny} from './types'
import type {PathBasedReducer} from './utils/PathBasedReducer'
import updateDeep from './utils/updateDeep'
import prism from './prism/prism'
@ -176,39 +176,22 @@ export default class Atom<State> implements IdentityPrismProvider {
return path.length === 0 ? this.getState() : get(this.getState(), path)
}
/**
* Creates a new state object from the current one, where the value at `path`
* is replaced by the return value of `reducer`, then sets it.
*
* @remarks
* Doesn't mutate the old state, and preserves referential equality between
* values of the old state and the new state where possible.
*
* @example
* ```ts
* someAtom.getIn(['a']) // 1
* someAtom.reduceState(['a'], (state) => state + 1);
* someAtom.getIn(['a']) // 2
* ```
*
* @param path - The path to call the reducer at.
* @param reducer - The function to use for creating the new state.
*/
// TODO: Why is this a property and not a method?
reduceState: PathBasedReducer<State, State> = (
path: $IntentionalAny[],
reducer: $IntentionalAny,
) => {
const newState = updateDeep(this.getState(), path, reducer)
this.setState(newState)
return newState
reduce(fn: (state: State) => State) {
this.set(fn(this.get()))
}
/**
* Sets the state of the atom at `path`.
*/
setIn(path: $FixMe[], val: $FixMe) {
return this.reduceState(path, () => val)
reduceByPointer<S>(
fn: (p: Pointer<State>) => Pointer<S>,
reducer: (s: S) => S,
) {
const pointer = fn(this.pointer)
const path = getPointerParts(pointer).path
const newState = updateDeep(this.get(), path, reducer)
this.set(newState)
}
setByPointer<S>(fn: (p: Pointer<State>) => Pointer<S>, val: S) {
this.reduceByPointer(fn, () => val)
}
private _checkUpdates(scope: Scope, oldState: unknown, newState: unknown) {

View file

@ -6,16 +6,16 @@ import iterateOver from './iterateOver'
describe(`iterateOver()`, () => {
test('it should work', () => {
const a = new Atom({a: 0})
let iter = iterateOver(a.pointer.a)
const a = new Atom(0)
let iter = iterateOver(a.pointer)
expect(iter.next().value).toEqual(0)
a.setIn(['a'], 1)
a.setIn(['a'], 2)
a.set(1)
a.set(2)
expect(iter.next()).toMatchObject({value: 2, done: false})
iter.return()
iter = iterateOver(a.pointer.a)
iter = iterateOver(a.pointer)
expect(iter.next().value).toEqual(2)
a.setIn(['a'], 3)
a.set(3)
expect(iter.next()).toMatchObject({done: false, value: 3})
iter.return()
})

View file

@ -25,7 +25,7 @@ describe('prism', () => {
changes.push(c)
})
o.reduceState(['foo'], () => 'foo2')
o.reduce(({foo}) => ({foo: 'foo2'}))
ticker.tick()
expect(changes).toMatchObject(['foo2boo'])
})
@ -43,11 +43,11 @@ describe('prism', () => {
describe('prism.ref()', () => {
it('should work', () => {
const theAtom: Atom<{n: number}> = new Atom({n: 2})
const theAtom: Atom<number> = new Atom(2)
const isEvenD = prism((): {isEven: boolean} => {
const ref = prism.ref<{isEven: boolean} | undefined>('cache', undefined)
const currentN = val(theAtom.pointer.n)
const currentN = val(theAtom.pointer)
const isEven = currentN % 2 === 0
if (ref.current && ref.current.isEven === isEven) {
@ -60,20 +60,20 @@ describe('prism', () => {
const iterator = iterateAndCountTicks(isEvenD)
theAtom.reduceState(['n'], () => 3)
theAtom.reduce(() => 3)
expect(iterator.next().value).toMatchObject({
value: {isEven: false},
ticks: 0,
})
theAtom.reduceState(['n'], () => 5)
theAtom.reduceState(['n'], () => 7)
theAtom.reduce(() => 5)
theAtom.reduce(() => 7)
expect(iterator.next().value).toMatchObject({
value: {isEven: false},
ticks: 1,
})
theAtom.reduceState(['n'], () => 2)
theAtom.reduceState(['n'], () => 4)
theAtom.reduce(() => 2)
theAtom.reduce(() => 4)
expect(iterator.next().value).toMatchObject({
value: {isEven: true},
ticks: 1,
@ -91,10 +91,10 @@ describe('prism', () => {
const sequence: unknown[] = []
let deps: unknown[] = []
const a = new Atom({letter: 'a'})
const a = new Atom('a')
const prsm = prism(() => {
const n = val(a.pointer.letter)
const n = val(a.pointer)
const iterationAtTimeOfCall = iteration
sequence.push({prismCall: iterationAtTimeOfCall})
@ -120,14 +120,14 @@ describe('prism', () => {
sequence.length = 0
iteration++
a.setIn(['letter'], 'b')
a.set('b')
ticker.tick()
expect(sequence).toMatchObject([{prismCall: 1}, {change: 'b'}])
sequence.length = 0
deps = [1]
iteration++
a.setIn(['letter'], 'c')
a.set('c')
ticker.tick()
expect(sequence).toMatchObject([
{prismCall: 2},
@ -151,10 +151,10 @@ describe('prism', () => {
const sequence: unknown[] = []
let deps: unknown[] = []
const a = new Atom({letter: 'a'})
const a = new Atom('a')
const prsm = prism(() => {
const n = val(a.pointer.letter)
const n = val(a.pointer)
const iterationAtTimeOfCall = iteration
sequence.push({prismCall: iterationAtTimeOfCall})
@ -184,7 +184,7 @@ describe('prism', () => {
sequence.length = 0
iteration++
a.setIn(['letter'], 'b')
a.set('b')
ticker.tick()
expect(sequence).toMatchObject([
{prismCall: 1},
@ -195,7 +195,7 @@ describe('prism', () => {
deps = [1]
iteration++
a.setIn(['letter'], 'c')
a.set('c')
ticker.tick()
expect(sequence).toMatchObject([
{prismCall: 2},

View file

@ -1,133 +0,0 @@
export type PathBasedReducer<S, ReturnType> = {
<
A0 extends keyof S,
A1 extends keyof S[A0],
A2 extends keyof S[A0][A1],
A3 extends keyof S[A0][A1][A2],
A4 extends keyof S[A0][A1][A2][A3],
A5 extends keyof S[A0][A1][A2][A3][A4],
A6 extends keyof S[A0][A1][A2][A3][A4][A5],
A7 extends keyof S[A0][A1][A2][A3][A4][A5][A6],
A8 extends keyof S[A0][A1][A2][A3][A4][A5][A6][A7],
A9 extends keyof S[A0][A1][A2][A3][A4][A5][A6][A7][A8],
A10 extends keyof S[A0][A1][A2][A3][A4][A5][A6][A7][A8][A9],
>(
addr: [A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10],
reducer: (
d: S[A0][A1][A2][A3][A4][A5][A6][A7][A8][A9][A10],
) => S[A0][A1][A2][A3][A4][A5][A6][A7][A8][A9][A10],
): ReturnType
<
A0 extends keyof S,
A1 extends keyof S[A0],
A2 extends keyof S[A0][A1],
A3 extends keyof S[A0][A1][A2],
A4 extends keyof S[A0][A1][A2][A3],
A5 extends keyof S[A0][A1][A2][A3][A4],
A6 extends keyof S[A0][A1][A2][A3][A4][A5],
A7 extends keyof S[A0][A1][A2][A3][A4][A5][A6],
A8 extends keyof S[A0][A1][A2][A3][A4][A5][A6][A7],
A9 extends keyof S[A0][A1][A2][A3][A4][A5][A6][A7][A8],
>(
addr: [A0, A1, A2, A3, A4, A5, A6, A7, A8, A9],
reducer: (
d: S[A0][A1][A2][A3][A4][A5][A6][A7][A8][A9],
) => S[A0][A1][A2][A3][A4][A5][A6][A7][A8][A9],
): ReturnType
<
A0 extends keyof S,
A1 extends keyof S[A0],
A2 extends keyof S[A0][A1],
A3 extends keyof S[A0][A1][A2],
A4 extends keyof S[A0][A1][A2][A3],
A5 extends keyof S[A0][A1][A2][A3][A4],
A6 extends keyof S[A0][A1][A2][A3][A4][A5],
A7 extends keyof S[A0][A1][A2][A3][A4][A5][A6],
A8 extends keyof S[A0][A1][A2][A3][A4][A5][A6][A7],
>(
addr: [A0, A1, A2, A3, A4, A5, A6, A7, A8],
reducer: (
d: S[A0][A1][A2][A3][A4][A5][A6][A7][A8],
) => S[A0][A1][A2][A3][A4][A5][A6][A7][A8],
): ReturnType
<
A0 extends keyof S,
A1 extends keyof S[A0],
A2 extends keyof S[A0][A1],
A3 extends keyof S[A0][A1][A2],
A4 extends keyof S[A0][A1][A2][A3],
A5 extends keyof S[A0][A1][A2][A3][A4],
A6 extends keyof S[A0][A1][A2][A3][A4][A5],
A7 extends keyof S[A0][A1][A2][A3][A4][A5][A6],
>(
addr: [A0, A1, A2, A3, A4, A5, A6, A7],
reducer: (
d: S[A0][A1][A2][A3][A4][A5][A6][A7],
) => S[A0][A1][A2][A3][A4][A5][A6][A7],
): ReturnType
<
A0 extends keyof S,
A1 extends keyof S[A0],
A2 extends keyof S[A0][A1],
A3 extends keyof S[A0][A1][A2],
A4 extends keyof S[A0][A1][A2][A3],
A5 extends keyof S[A0][A1][A2][A3][A4],
A6 extends keyof S[A0][A1][A2][A3][A4][A5],
>(
addr: [A0, A1, A2, A3, A4, A5, A6],
reducer: (
d: S[A0][A1][A2][A3][A4][A5][A6],
) => S[A0][A1][A2][A3][A4][A5][A6],
): ReturnType
<
A0 extends keyof S,
A1 extends keyof S[A0],
A2 extends keyof S[A0][A1],
A3 extends keyof S[A0][A1][A2],
A4 extends keyof S[A0][A1][A2][A3],
A5 extends keyof S[A0][A1][A2][A3][A4],
>(
addr: [A0, A1, A2, A3, A4, A5],
reducer: (d: S[A0][A1][A2][A3][A4][A5]) => S[A0][A1][A2][A3][A4][A5],
): ReturnType
<
A0 extends keyof S,
A1 extends keyof S[A0],
A2 extends keyof S[A0][A1],
A3 extends keyof S[A0][A1][A2],
A4 extends keyof S[A0][A1][A2][A3],
>(
addr: [A0, A1, A2, A3, A4],
reducer: (d: S[A0][A1][A2][A3][A4]) => S[A0][A1][A2][A3][A4],
): ReturnType
<
A0 extends keyof S,
A1 extends keyof S[A0],
A2 extends keyof S[A0][A1],
A3 extends keyof S[A0][A1][A2],
>(
addr: [A0, A1, A2, A3],
reducer: (d: S[A0][A1][A2][A3]) => S[A0][A1][A2][A3],
): ReturnType
<A0 extends keyof S, A1 extends keyof S[A0], A2 extends keyof S[A0][A1]>(
addr: [A0, A1, A2],
reducer: (d: S[A0][A1][A2]) => S[A0][A1][A2],
): ReturnType
<A0 extends keyof S, A1 extends keyof S[A0]>(
addr: [A0, A1],
reducer: (d: S[A0][A1]) => S[A0][A1],
): ReturnType
<A0 extends keyof S>(addr: [A0], reducer: (d: S[A0]) => S[A0]): ReturnType
(addr: undefined[], reducer: (d: S) => S): ReturnType
}