Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor: use shim only version of uSES #2

Merged
merged 2 commits into from
Dec 8, 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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-tivity",
"version": "1.0.0-beta.0",
"version": "1.0.0-beta.1",
"description": "State solution for React",
"private": true,
"main": "./dist/index.js",
Expand Down
41 changes: 41 additions & 0 deletions src/__tests__/create-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -179,3 +179,44 @@ describe('create tests', () => {
expect(cb).toHaveBeenCalledTimes(1)
})
})

test('Sets state asynchronously', async () => {
type State = {
values: any
setValues: (state: State) => void
}

const requestData = () =>
new Promise(resolve => {
setTimeout(() => resolve([{ value: 'foo' }, { value: 'bar' }]), 1000)
})

const useHook = create<State>({
values: [],
setValues: async state => {
let res = await requestData()
state.values = await res
}
})

function Component() {
let { values, setValues } = useHook()

return (
<div>
<div>
{values.length ? `${values[0].value}&${values[1].value}` : 'no value'}
</div>
<button onClick={setValues}>change</button>
</div>
)
}

let { findByText, getByText } = render(<Component />)

await findByText('no value')

fireEvent.click(getByText('change'))

await findByText('foo&bar')
})
2 changes: 1 addition & 1 deletion src/__tests__/unit/storage-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ describe('Internal storage tests', () => {
'[react-tivity] window undefined failed to build localStorage falling back to noopStorage'
)
})
})
})
10 changes: 5 additions & 5 deletions src/apis/create.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { initStore, useStore } from '../utils'
import type { Obj, State, CreateHook } from '../utils'

export function create<StateObj extends Obj>(
arg: StateObj | (() => StateObj)
): CreateHook<StateObj> {
export function create<TState extends Obj>(
arg: TState | (() => TState)
): CreateHook<TState> {
const initObj = typeof arg === 'function' ? arg() : arg
const store = initStore<State<StateObj>>(initObj)
const store = initStore<State<TState>>(initObj)

const hook = () => useStore<StateObj>(store)
const hook = () => useStore<State<TState>>(store)

const useHook = Object.assign(hook, {
subscribe: store.subscribe,
Expand Down
2 changes: 1 addition & 1 deletion src/apis/persist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ export function persist<TState extends Obj, TArgB = undefined, Action = any>(
store.setStateImpl({ ...toSet, _status: true })
})

store.subscribe(saveToStorage)
store.subscribe(() => saveToStorage())

const hook = () =>
useStore<
Expand Down
14 changes: 8 additions & 6 deletions src/utils/initStore.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import type { Obj } from './types'

export function initStore<State>(initObj: Obj) {
const subscribers = new Set<() => void | any>()
const subscribers = new Set<(prev: Obj, next: Obj) => void | any>()
const copyObj = (obj: Obj = getSnapshot()) => JSON.parse(JSON.stringify(obj))

const setStateImpl = (nextState: Obj) => {
state = Object.assign({}, state, nextState)
subscribers.forEach(cb => cb())
state = Object.assign({}, state, copyObj(nextState))
subscribers.forEach(cb => cb(prevState, state))
prevState = state
}

const setState = (method: any, args: any) => method(proxiedState, ...args)
Expand All @@ -21,6 +23,7 @@ export function initStore<State>(initObj: Obj) {
}
})

let prevState = state
const getSnapshot = () => state

const subscribe = (cb: () => void | any) => {
Expand All @@ -29,7 +32,7 @@ export function initStore<State>(initObj: Obj) {
}

const proxiedState = ((): State => {
let stateCopy = JSON.parse(JSON.stringify(getSnapshot()))
let stateCopy = copyObj()

Object.keys(state).forEach(key => {
if (typeof state[key] === 'function') {
Expand All @@ -49,8 +52,7 @@ export function initStore<State>(initObj: Obj) {
},
set(obj: Obj, prop: string, value: any) {
obj[prop] = value
if (Object.keys(obj).some(key => typeof obj[key] === 'function'))
setStateImpl(obj)
setStateImpl(stateCopy)
return true
}
}
Expand Down
82 changes: 43 additions & 39 deletions src/utils/uSES.ts
Original file line number Diff line number Diff line change
@@ -1,54 +1,58 @@
import useSyncExternalStoreExports from 'use-sync-external-store/shim/with-selector.js'
import { useSyncExternalStore } from 'use-sync-external-store/shim/index.js'
import type { Obj } from './types'
import { useRef } from 'react'
import { useRef, useMemo } from 'react'

const { useSyncExternalStoreWithSelector } = useSyncExternalStoreExports
const isObject = (x: any) => {
return x ? (typeof x === 'object' ? true : false) : false
}

export function useStore<TState extends Obj>(store: any) {
const stateDepRefs = useRef<string[]>([])
const isDeepEqual = (a: any, b: any) => {
const sameType = typeof a === typeof b ? true : false
let equal = true
if (sameType && isObject(b)) {
const bKeys = Object.keys(b)

bKeys.forEach(key => {
if (!isObject(b[key])) {
if (a[key] !== b[key]) equal = false
} else {
if (!isDeepEqual(a[key], b[key])) equal = false
}
})

const isObject = (x: any) => {
return x ? (typeof x === 'object' ? true : false) : false
return equal
}
return a === b
}

const isEqual = (a: any, b: any) => {
const sameType = typeof a === typeof b ? true : false
let equal = true
if (sameType && isObject(a)) {
const aKeys = Object.keys(a)

aKeys.forEach(key => {
if (!isObject(a[key])) {
if (a[key] !== b[key]) equal = false
} else {
if (!isEqual(a[key], b[key])) equal = false
}
})
export function useStore<TState extends Obj>(store: any): TState {
const stateDepRefs = useRef<string[]>([])

return equal
}
return a === b
}
const isEqual = (prev: Obj, next: Obj) =>
stateDepRefs.current.every(
slice => isDeepEqual(prev[slice], next[slice]) === true
)

const state = useSyncExternalStoreWithSelector(
store.subscribe,
store.getSnapshot,
store.getSnapshot,
(s: TState) => s,
(prev: Obj, next: Obj) => {
for (let slice in prev) {
if (stateDepRefs.current.includes(slice)) {
if (!isEqual(prev[slice], next[slice])) {
return false
}
}
}
return true
const getSnapshot = useMemo(() => {
let memoized = store.getSnapshot()
return () => {
let current = store.getSnapshot()
return Object.is(memoized, current) ? memoized : (memoized = current)
}
}, [store.getSnapshot()])

const state = useSyncExternalStore(
cb => {
return store.subscribe((prev: Obj, next: Obj) => {
if (!isEqual(prev, next)) cb()
})
},
getSnapshot,
getSnapshot
)

const handler = {
get(obj: TState, prop: string) {
get(obj: Obj, prop: string) {
if (
!stateDepRefs.current.includes(prop) &&
typeof obj[prop] !== 'function'
Expand Down