Skip to content

Commit 56ae1a4

Browse files
authored
feat(core): experimental unstable versioned write for limited use case (#854)
* feat(core): experimental version object * refactor * fix test name in ci * revert test/query * empty commit * fix workflow file * remove delay in urql/atomWithQuery * fix test * tweak query test * fix atomWithQuery in urql * revert again query timing * wip: non-working commit, trying another path * another approach with provider which requires pure write * fix logic, add tests, skip tests * refactor, simplify * wip: refactor * skip a test * skip one more test * skip 16.8.6 * only notify changed atom state * remove extra falsy check * fix types * wip: a workaround for now * fix logic, skip one test for now * a minor refactor * add some notes * suspense promise should not throw
1 parent 5e6da03 commit 56ae1a4

14 files changed

Lines changed: 482 additions & 238 deletions

.github/workflows/test-multiple-versions.yml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@ jobs:
2424
- name: Test Default without Provider
2525
run: yarn test:ci
2626
env:
27-
PROVIDER_LESS_MODE: 'true'
27+
PROVIDER_MODE: 'PROVIDER_LESS'
28+
- name: Test Default with Versioned Write
29+
run: yarn test:ci
30+
env:
31+
PROVIDER_MODE: 'VERSIONED_WRITE'
2832

2933
test_matrix:
3034
runs-on: ubuntu-latest
@@ -37,9 +41,10 @@ jobs:
3741
- 17.0.0
3842
- 18.0.0-rc.0
3943
- 0.0.0-experimental-aa8f2bdbc-20211215
40-
mode: [withProvider, withoutProvider]
44+
mode: [NORMAL, PROVIDER_LESS, VERSIONED_WRITE]
4145
testing: [default, alpha]
4246
exclude:
47+
- { react: 16.8.6, mode: VERSIONED_WRITE }
4348
- { react: 16.8.6, testing: alpha }
4449
- { react: 16.9.0, testing: alpha }
4550
- { react: 17.0.0, testing: alpha }
@@ -71,4 +76,4 @@ jobs:
7176
yarn add -D react@${{ matrix.react }} react-dom@${{ matrix.react }}
7277
yarn test:ci
7378
env:
74-
PROVIDER_LESS_MODE: ${{ matrix.mode == 'withoutProvider' }}
79+
PROVIDER_MODE: ${{ matrix.mode }}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@
127127
"eslint": "eslint --fix '*.{js,json}' '{src,tests,benchmarks}/**/*.{ts,tsx}'",
128128
"eslint:ci": "eslint '*.{js,json}' '{src,tests,benchmarks}/**/*.{ts,tsx}'",
129129
"pretest": "tsc --noEmit",
130-
"test": "jest && jest --setupFiles ./tests/setProviderLessMode.ts",
130+
"test": "jest && jest --setupFiles ./tests/setProviderLessMode.ts && jest --setupFiles ./tests/setVersionedWriteMode.ts",
131131
"test:ci": "jest",
132132
"test:dev": "jest --watch --no-coverage",
133133
"test:coverage:watch": "jest --watch",

src/core/Provider.ts

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,55 @@ import type { Atom, Scope } from './atom'
1010
import { createScopeContainer, getScopeContext } from './contexts'
1111
import type { ScopeContainer } from './contexts'
1212
import {
13+
COMMIT_ATOM,
1314
DEV_GET_ATOM_STATE,
1415
DEV_GET_MOUNTED,
1516
DEV_GET_MOUNTED_ATOMS,
1617
DEV_SUBSCRIBE_STATE,
1718
} from './store'
18-
import type { AtomState, Store } from './store'
19+
import type { AtomState, Store, VersionObject } from './store'
1920

2021
export const Provider = ({
22+
children,
2123
initialValues,
2224
scope,
23-
children,
25+
unstable_enableVersionedWrite,
2426
}: PropsWithChildren<{
2527
initialValues?: Iterable<readonly [Atom<unknown>, unknown]>
2628
scope?: Scope
29+
/**
30+
* This is an unstable experimental feature for React 18.
31+
* When this is enabled, a) write function must be pure
32+
* (read function must be pure regardless of this),
33+
* b) React will show warning in DEV mode,
34+
* c) then state branching works.
35+
*/
36+
unstable_enableVersionedWrite?: boolean
2737
}>) => {
38+
const [version, setVersion] = useState<VersionObject>()
39+
useEffect(() => {
40+
if (version) {
41+
;(scopeContainerRef.current as ScopeContainer).s[COMMIT_ATOM](
42+
null,
43+
version
44+
)
45+
delete version.p
46+
}
47+
}, [version])
48+
2849
const scopeContainerRef = useRef<ScopeContainer>()
2950
if (!scopeContainerRef.current) {
3051
// lazy initialization
3152
scopeContainerRef.current = createScopeContainer(initialValues)
53+
if (unstable_enableVersionedWrite) {
54+
scopeContainerRef.current.w = (write) => {
55+
setVersion((parentVersion) => {
56+
const nextVersion = parentVersion ? { p: parentVersion } : {}
57+
write(nextVersion)
58+
return nextVersion
59+
})
60+
}
61+
}
3262
}
3363

3464
if (
@@ -79,7 +109,7 @@ const stateToPrintable = ([store, atoms]: [Store, Atom<unknown>[]]) =>
79109
// We keep a reference to the atoms in Provider's registeredAtoms in dev mode,
80110
// so atoms aren't garbage collected by the WeakMap of mounted atoms
81111
const useDebugState = (scopeContainer: ScopeContainer) => {
82-
const store = scopeContainer.s
112+
const { s: store } = scopeContainer
83113
const [atoms, setAtoms] = useState<Atom<unknown>[]>([])
84114
useEffect(() => {
85115
const callback = () => {

src/core/contexts.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@ import type { Atom, Scope } from './atom'
44
import { createStore } from './store'
55
import type { Store } from './store'
66

7+
type VersionedWrite = (write: (version?: object) => void) => void
8+
79
export type ScopeContainer = {
810
s: Store
11+
w?: VersionedWrite
912
}
1013

1114
export const createScopeContainer = (

0 commit comments

Comments
 (0)