-
-
Notifications
You must be signed in to change notification settings - Fork 649
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: jotai/valtio * update tests, add csb example * update valtio * chore: update size snapshot
- Loading branch information
Showing
7 changed files
with
217 additions
and
100 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
This doc describes `jotai/valtio` bundle. | ||
|
||
Jotai's state resides in React, but sometimes it would be nice | ||
to intract with the world outside React. | ||
Valtio provides a proxy interface that can be used to store some values | ||
and sync with atoms in jotai. | ||
This only uses the vanilla api of valtio. | ||
|
||
## Install | ||
|
||
You have to install `valtio` to access this bundle and its functions. | ||
|
||
``` | ||
npm install valtio | ||
# or | ||
yarn add valtio | ||
``` | ||
|
||
## atomWithProxy | ||
|
||
`atomWithProxy` creates a new atom with valtio proxy. | ||
It's two-way binding and you can change the value from both ends. | ||
|
||
```js | ||
import { useAtom } from 'jotai' | ||
import { atomWithProxy } from 'jotai/valtio' | ||
import { proxy } from 'valtio/vanilla' | ||
|
||
const proxyState = proxy({ count: 0 }) | ||
const stateAtom = atomWithProxy(proxyState) | ||
const Counter: React.FC = () => { | ||
const [state, setState] = useAtom(stateAtom) | ||
|
||
return ( | ||
<> | ||
count: {state.count} | ||
<button | ||
onClick={() => | ||
setState((prev) => ({ ...prev, count: prev.count + 1 })) | ||
}> | ||
button | ||
</button> | ||
</> | ||
) | ||
} | ||
``` | ||
|
||
### Examples | ||
|
||
https://codesandbox.io/s/react-typescript-forked-f5u4l |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { atomWithProxy } from './valtio/atomWithProxy' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import { subscribe, snapshot } from 'valtio/vanilla' | ||
import { atom } from 'jotai' | ||
import type { SetStateAction, PrimitiveAtom } from '../core/types' | ||
|
||
const isObject = (x: unknown): x is object => | ||
typeof x === 'object' && x !== null | ||
|
||
const applyChanges = <T extends object>(proxyObject: T, prev: T, next: T) => { | ||
;(Object.keys(prev) as (keyof T)[]).forEach((key) => { | ||
if (!(key in next)) { | ||
delete proxyObject[key] | ||
} else if (Object.is(prev[key], next[key])) { | ||
// unchanged | ||
} else if (isObject(prev[key]) && isObject(next[key])) { | ||
applyChanges(proxyObject[key] as any, prev[key], next[key]) | ||
} else { | ||
proxyObject[key] = next[key] | ||
} | ||
}) | ||
;(Object.keys(next) as (keyof T)[]).forEach((key) => { | ||
if (!(key in prev)) { | ||
proxyObject[key] = next[key] | ||
} | ||
}) | ||
} | ||
|
||
export function atomWithProxy<Value extends object>(proxyObject: Value) { | ||
const baseAtom: PrimitiveAtom<Value> = atom(snapshot(proxyObject) as any) | ||
baseAtom.onMount = (setValue) => | ||
subscribe(proxyObject, () => { | ||
setValue(snapshot(proxyObject) as Value) | ||
}) | ||
const derivedAtom = atom( | ||
(get) => get(baseAtom), | ||
(get, _set, update: SetStateAction<Value>) => { | ||
const newValue = | ||
typeof update === 'function' | ||
? (update as (prev: Value) => Value)(get(baseAtom)) | ||
: update | ||
applyChanges(proxyObject, snapshot(proxyObject) as Value, newValue) | ||
} | ||
) | ||
return derivedAtom | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
import React from 'react' | ||
import { fireEvent, render } from '@testing-library/react' | ||
import { proxy, snapshot } from 'valtio/vanilla' | ||
import { Provider, useAtom } from '../../src/index' | ||
import { atomWithProxy } from '../../src/valtio' | ||
|
||
it('count state', async () => { | ||
const proxyState = proxy({ count: 0 }) | ||
const stateAtom = atomWithProxy(proxyState) | ||
const Counter: React.FC = () => { | ||
const [state, setState] = useAtom(stateAtom) | ||
|
||
return ( | ||
<> | ||
count: {state.count} | ||
<button | ||
onClick={() => | ||
setState((prev) => ({ ...prev, count: prev.count + 1 })) | ||
}> | ||
button | ||
</button> | ||
</> | ||
) | ||
} | ||
|
||
const { findByText, getByText } = render( | ||
<Provider> | ||
<Counter /> | ||
</Provider> | ||
) | ||
|
||
await findByText('count: 0') | ||
|
||
fireEvent.click(getByText('button')) | ||
await findByText('count: 1') | ||
expect(proxyState.count).toBe(1) | ||
|
||
++proxyState.count | ||
await findByText('count: 2') | ||
expect(proxyState.count).toBe(2) | ||
}) | ||
|
||
it('nested count state', async () => { | ||
const proxyState = proxy({ nested: { count: 0 }, other: {} }) | ||
const otherSnap = snapshot(proxyState.other) | ||
const stateAtom = atomWithProxy(proxyState) | ||
const Counter: React.FC = () => { | ||
const [state, setState] = useAtom(stateAtom) | ||
|
||
return ( | ||
<> | ||
count: {state.nested.count} | ||
<button | ||
onClick={() => | ||
setState((prev) => ({ | ||
...prev, | ||
nested: { ...prev.nested, count: prev.nested.count + 1 }, | ||
})) | ||
}> | ||
button | ||
</button> | ||
</> | ||
) | ||
} | ||
|
||
const { findByText, getByText } = render( | ||
<Provider> | ||
<Counter /> | ||
</Provider> | ||
) | ||
|
||
await findByText('count: 0') | ||
|
||
fireEvent.click(getByText('button')) | ||
await findByText('count: 1') | ||
expect(proxyState.nested.count).toBe(1) | ||
expect(otherSnap === snapshot(proxyState.other)).toBe(true) | ||
|
||
++proxyState.nested.count | ||
await findByText('count: 2') | ||
expect(proxyState.nested.count).toBe(2) | ||
expect(otherSnap === snapshot(proxyState.other)).toBe(true) | ||
}) |
Oops, something went wrong.
0e01c87
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs: