Comparing version 1.0.0 to 1.0.1
61
index.js
@@ -9,8 +9,2 @@ 'use strict'; | ||
/* | ||
export { | ||
unstable_createMutableSource as createMutableSource, | ||
unstable_useMutableSource as useMutableSource, | ||
} from 'react' | ||
*/ | ||
var TARGET = Symbol(); | ||
@@ -28,13 +22,3 @@ var GET_VERSION = Symbol(); | ||
var _useState = react.useState(function () { | ||
return [ | ||
/* [0] */ | ||
source, | ||
/* [1] */ | ||
getSnapshot, | ||
/* [2] */ | ||
subscribe, | ||
/* [3] */ | ||
currentVersion, | ||
/* [4] */ | ||
getSnapshot(source[TARGET])]; | ||
return [source, getSnapshot, subscribe, currentVersion, getSnapshot(source[TARGET])]; | ||
}), | ||
@@ -46,15 +30,11 @@ state = _useState[0], | ||
if (state[0] !== source || state[1] !== getSnapshot || state[2] !== subscribe || currentVersion !== state[3] && currentVersion !== lastVersion.current) { | ||
if (state[0] !== source || state[1] !== getSnapshot || state[2] !== subscribe) { | ||
currentSnapshot = getSnapshot(source[TARGET]); | ||
setState([ | ||
/* [0] */ | ||
source, | ||
/* [1] */ | ||
getSnapshot, | ||
/* [2] */ | ||
subscribe, | ||
/* [3] */ | ||
currentVersion, | ||
/* [4] */ | ||
currentSnapshot]); | ||
setState([source, getSnapshot, subscribe, currentVersion, currentSnapshot]); | ||
} else if (currentVersion !== state[3] && currentVersion !== lastVersion.current) { | ||
currentSnapshot = getSnapshot(source[TARGET]); | ||
if (!Object.is(currentSnapshot, state[4])) { | ||
setState([source, getSnapshot, subscribe, currentVersion, currentSnapshot]); | ||
} | ||
} | ||
@@ -79,20 +59,9 @@ | ||
if (prev[4] === nextSnapshot) { | ||
if (Object.is(prev[4], nextSnapshot)) { | ||
return prev; | ||
} | ||
return [ | ||
/* [0] */ | ||
prev[0], | ||
/* [1] */ | ||
prev[1], | ||
/* [2] */ | ||
prev[2], | ||
/* [3] */ | ||
nextVersion, | ||
/* [4] */ | ||
nextSnapshot]; | ||
return [prev[0], prev[1], prev[2], nextVersion, nextSnapshot]; | ||
}); | ||
} catch (e) { | ||
// schedule update | ||
setState(function (prev) { | ||
@@ -164,7 +133,5 @@ return [].concat(prev); | ||
if (lastAffected.current && !proxyCompare.isDeepChanged(prevSnapshot.current, nextSnapshot, lastAffected.current, new WeakMap())) { | ||
// not changed | ||
return; | ||
} | ||
} catch (e) {// ignore if a promise or something is thrown | ||
} | ||
} catch (e) {} | ||
@@ -178,3 +145,2 @@ prevSnapshot.current = nextSnapshot; | ||
if (typeof process === 'object' && process.env.NODE_ENV !== 'production') { | ||
// eslint-disable-next-line react-hooks/rules-of-hooks | ||
useAffectedDebugValue(currSnapshot, affected); | ||
@@ -185,4 +151,3 @@ } | ||
return new WeakMap(); | ||
}, []); // per-hook proxyCache | ||
}, []); | ||
return proxyCompare.createDeepProxy(currSnapshot, affected, proxyCache); | ||
@@ -189,0 +154,0 @@ }; |
@@ -6,8 +6,2 @@ import { snapshot, subscribe, getVersion } from './vanilla'; | ||
/* | ||
export { | ||
unstable_createMutableSource as createMutableSource, | ||
unstable_useMutableSource as useMutableSource, | ||
} from 'react' | ||
*/ | ||
const TARGET = Symbol(); | ||
@@ -23,32 +17,32 @@ const GET_VERSION = Symbol(); | ||
const [state, setState] = useState(() => [ | ||
/* [0] */ | ||
source, | ||
/* [1] */ | ||
getSnapshot, | ||
/* [2] */ | ||
subscribe, | ||
/* [3] */ | ||
currentVersion, | ||
/* [4] */ | ||
getSnapshot(source[TARGET])]); | ||
let currentSnapshot = state[4]; | ||
if (state[0] !== source || state[1] !== getSnapshot || state[2] !== subscribe || currentVersion !== state[3] && currentVersion !== lastVersion.current) { | ||
currentSnapshot = getSnapshot(source[TARGET]); | ||
setState([ | ||
/* [0] */ | ||
source, | ||
/* [1] */ | ||
getSnapshot, | ||
/* [2] */ | ||
subscribe, | ||
/* [3] */ | ||
currentVersion, | ||
/* [4] */ | ||
currentSnapshot]); | ||
getSnapshot(source[TARGET]) | ||
]); | ||
let currentSnapshot = state[4]; | ||
if (state[0] !== source || state[1] !== getSnapshot || state[2] !== subscribe) { | ||
currentSnapshot = getSnapshot(source[TARGET]); | ||
setState([ | ||
source, | ||
getSnapshot, | ||
subscribe, | ||
currentVersion, | ||
currentSnapshot | ||
]); | ||
} else if (currentVersion !== state[3] && currentVersion !== lastVersion.current) { | ||
currentSnapshot = getSnapshot(source[TARGET]); | ||
if (!Object.is(currentSnapshot, state[4])) { | ||
setState([ | ||
source, | ||
getSnapshot, | ||
subscribe, | ||
currentVersion, | ||
currentSnapshot | ||
]); | ||
} | ||
} | ||
useEffect(() => { | ||
let didUnsubscribe = false; | ||
const checkForUpdates = () => { | ||
@@ -58,3 +52,2 @@ if (didUnsubscribe) { | ||
} | ||
try { | ||
@@ -64,29 +57,21 @@ const nextSnapshot = getSnapshot(source[TARGET]); | ||
lastVersion.current = nextVersion; | ||
setState(prev => { | ||
setState((prev) => { | ||
if (prev[0] !== source || prev[1] !== getSnapshot || prev[2] !== subscribe) { | ||
return prev; | ||
} | ||
if (prev[4] === nextSnapshot) { | ||
if (Object.is(prev[4], nextSnapshot)) { | ||
return prev; | ||
} | ||
return [ | ||
/* [0] */ | ||
prev[0], | ||
/* [1] */ | ||
prev[1], | ||
/* [2] */ | ||
prev[2], | ||
/* [3] */ | ||
nextVersion, | ||
/* [4] */ | ||
nextSnapshot]; | ||
prev[0], | ||
prev[1], | ||
prev[2], | ||
nextVersion, | ||
nextSnapshot | ||
]; | ||
}); | ||
} catch (e) { | ||
// schedule update | ||
setState(prev => [...prev]); | ||
setState((prev) => [...prev]); | ||
} | ||
}; | ||
const unsubscribe = subscribe(source[TARGET], checkForUpdates); | ||
@@ -102,5 +87,4 @@ checkForUpdates(); | ||
const isSSR = typeof window === 'undefined' || /ServerSideRendering/.test(window.navigator && window.navigator.userAgent); | ||
const isSSR = typeof window === "undefined" || /ServerSideRendering/.test(window.navigator && window.navigator.userAgent); | ||
const useIsomorphicLayoutEffect = isSSR ? useEffect : useLayoutEffect; | ||
const useAffectedDebugValue = (state, affected) => { | ||
@@ -113,15 +97,11 @@ const pathList = useRef(); | ||
}; | ||
const mutableSourceCache = new WeakMap(); | ||
const getMutableSource = proxyObject => { | ||
const getMutableSource = (proxyObject) => { | ||
if (!mutableSourceCache.has(proxyObject)) { | ||
mutableSourceCache.set(proxyObject, createMutableSource(proxyObject, getVersion)); | ||
} | ||
return mutableSourceCache.get(proxyObject); | ||
}; | ||
const useSnapshot = (proxyObject, options) => { | ||
const [, forceUpdate] = useReducer(c => c + 1, 0); | ||
const [, forceUpdate] = useReducer((c) => c + 1, 0); | ||
const affected = new WeakMap(); | ||
@@ -136,3 +116,2 @@ const lastAffected = useRef(); | ||
lastAffected.current = affected; | ||
if (prevSnapshot.current !== lastSnapshot.current && isDeepChanged(prevSnapshot.current, lastSnapshot.current, affected, new WeakMap())) { | ||
@@ -144,14 +123,11 @@ prevSnapshot.current = lastSnapshot.current; | ||
const notifyInSync = options == null ? void 0 : options.sync; | ||
const sub = useCallback((proxyObject, cb) => subscribe(proxyObject, () => { | ||
const nextSnapshot = snapshot(proxyObject); | ||
const sub = useCallback((proxyObject2, cb) => subscribe(proxyObject2, () => { | ||
const nextSnapshot = snapshot(proxyObject2); | ||
lastSnapshot.current = nextSnapshot; | ||
try { | ||
if (lastAffected.current && !isDeepChanged(prevSnapshot.current, nextSnapshot, lastAffected.current, new WeakMap())) { | ||
// not changed | ||
return; | ||
} | ||
} catch (e) {// ignore if a promise or something is thrown | ||
} catch (e) { | ||
} | ||
prevSnapshot.current = nextSnapshot; | ||
@@ -161,10 +137,6 @@ cb(); | ||
const currSnapshot = useMutableSource(getMutableSource(proxyObject), snapshot, sub); | ||
if (typeof process === 'object' && process.env.NODE_ENV !== 'production') { | ||
// eslint-disable-next-line react-hooks/rules-of-hooks | ||
if (typeof process === "object" && process.env.NODE_ENV !== "production") { | ||
useAffectedDebugValue(currSnapshot, affected); | ||
} | ||
const proxyCache = useMemo(() => new WeakMap(), []); // per-hook proxyCache | ||
const proxyCache = useMemo(() => new WeakMap(), []); | ||
return createDeepProxy(currSnapshot, affected, proxyCache); | ||
@@ -171,0 +143,0 @@ }; |
35
macro.js
@@ -10,19 +10,19 @@ 'use strict'; | ||
function _interopNamespace(e) { | ||
if (e && e.__esModule) return e; | ||
var n = Object.create(null); | ||
if (e) { | ||
Object.keys(e).forEach(function (k) { | ||
if (k !== 'default') { | ||
var d = Object.getOwnPropertyDescriptor(e, k); | ||
Object.defineProperty(n, k, d.get ? d : { | ||
enumerable: true, | ||
get: function () { | ||
return e[k]; | ||
} | ||
}); | ||
} | ||
if (e && e.__esModule) return e; | ||
var n = Object.create(null); | ||
if (e) { | ||
Object.keys(e).forEach(function (k) { | ||
if (k !== 'default') { | ||
var d = Object.getOwnPropertyDescriptor(e, k); | ||
Object.defineProperty(n, k, d.get ? d : { | ||
enumerable: true, | ||
get: function () { | ||
return e[k]; | ||
} | ||
}); | ||
} | ||
n['default'] = e; | ||
return Object.freeze(n); | ||
} | ||
}); | ||
} | ||
n['default'] = e; | ||
return Object.freeze(n); | ||
} | ||
@@ -47,4 +47,3 @@ | ||
Identifier: function Identifier(p) { | ||
if (inFunction === 0 && // in render | ||
p.node !== proxy && p.node.name === proxy.name) { | ||
if (inFunction === 0 && p.node !== proxy && p.node.name === proxy.name) { | ||
p.node.name = snap.name; | ||
@@ -51,0 +50,0 @@ } |
@@ -5,24 +5,21 @@ import * as t from '@babel/types'; | ||
const macro = ({ | ||
references | ||
}) => { | ||
var _references$useProxy; | ||
(_references$useProxy = references.useProxy) == null ? void 0 : _references$useProxy.forEach(path => { | ||
var _path$parentPath$getF; | ||
const hook = addNamed(path, 'useSnapshot', 'valtio'); | ||
const proxy = path.parentPath.get('arguments.0').node; | ||
if (!t.isIdentifier(proxy)) throw new MacroError('no proxy object'); | ||
const macro = ({references}) => { | ||
var _a; | ||
(_a = references.useProxy) == null ? void 0 : _a.forEach((path) => { | ||
var _a2; | ||
const hook = addNamed(path, "useSnapshot", "valtio"); | ||
const proxy = path.parentPath.get("arguments.0").node; | ||
if (!t.isIdentifier(proxy)) | ||
throw new MacroError("no proxy object"); | ||
const snap = t.identifier(`valtio_macro_snap_${proxy.name}`); | ||
path.parentPath.parentPath.replaceWith(t.variableDeclaration('const', [t.variableDeclarator(snap, t.callExpression(hook, [proxy]))])); | ||
path.parentPath.parentPath.replaceWith(t.variableDeclaration("const", [ | ||
t.variableDeclarator(snap, t.callExpression(hook, [proxy])) | ||
])); | ||
let inFunction = 0; | ||
(_path$parentPath$getF = path.parentPath.getFunctionParent()) == null ? void 0 : _path$parentPath$getF.traverse({ | ||
(_a2 = path.parentPath.getFunctionParent()) == null ? void 0 : _a2.traverse({ | ||
Identifier(p) { | ||
if (inFunction === 0 && // in render | ||
p.node !== proxy && p.node.name === proxy.name) { | ||
if (inFunction === 0 && p.node !== proxy && p.node.name === proxy.name) { | ||
p.node.name = snap.name; | ||
} | ||
}, | ||
Function: { | ||
@@ -32,7 +29,5 @@ enter() { | ||
}, | ||
exit() { | ||
--inFunction; | ||
} | ||
} | ||
@@ -42,7 +37,4 @@ }); | ||
}; | ||
var macro$1 = createMacro(macro, {configName: "valtio"}); | ||
var macro$1 = createMacro(macro, { | ||
configName: 'valtio' | ||
}); | ||
export default macro$1; |
{ | ||
"name": "valtio", | ||
"private": false, | ||
"version": "1.0.0", | ||
"version": "1.0.1", | ||
"description": "💊 Valtio makes proxy-state simple for React and Vanilla", | ||
@@ -6,0 +6,0 @@ "main": "index.js", |
@@ -5,3 +5,75 @@ import type { NonPromise } from './vanilla'; | ||
}; | ||
/** | ||
* useSnapshot | ||
* | ||
* Create a local snapshot that catches changes. This hook actually returns a wrapped snapshot in a proxy for | ||
* render optimization instead of a plain object compared to `snapshot()` method. | ||
* Rule of thumb: read from snapshots, mutate the source. | ||
* The component will only re-render when the parts of the state you access have changed, it is render-optimized. | ||
* | ||
* @example A | ||
* function Counter() { | ||
* const snap = useSnapshot(state) | ||
* return ( | ||
* <div> | ||
* {snap.count} | ||
* <button onClick={() => ++state.count}>+1</button> | ||
* </div> | ||
* ) | ||
* } | ||
* | ||
* [Notes] | ||
* Every object inside your proxy also becomes a proxy (if you don't use "ref"), so you can also use them to create | ||
* the local snapshot as seen on example B. | ||
* | ||
* @example B | ||
* function ProfileName() { | ||
* const snap = useSnapshot(state.profile) | ||
* return ( | ||
* <div> | ||
* {snap.name} | ||
* </div> | ||
* ) | ||
* } | ||
* | ||
* Beware that you still can replace the child proxy with something else so it will break your snapshot. You can see | ||
* above what happens with the original proxy when you replace the child proxy. | ||
* | ||
* > console.log(state) | ||
* { profile: { name: "valtio" } } | ||
* > childState = state.profile | ||
* > console.log(childState) | ||
* { name: "valtio" } | ||
* > state.profile.name = "react" | ||
* > console.log(childState) | ||
* { name: "react" } | ||
* > state.profile = { name: "new name" } | ||
* > console.log(childState) | ||
* { name: "react" } | ||
* > console.log(state) | ||
* { profile: { name: "new name" } } | ||
* | ||
* `useSnapshot()` depends on the original reference of the child proxy so if you replace it with a new one, the component | ||
* that is subscribed to the old proxy won't receive new updates because it is still subscribed to the old one. | ||
* | ||
* In this case we recommend the example C or D. On both examples you don't need to worry with re-render, | ||
* because it is render-optimized. | ||
* | ||
* @example C | ||
* const snap = useSnapshot(state) | ||
* return ( | ||
* <div> | ||
* {snap.profile.name} | ||
* </div> | ||
* ) | ||
* | ||
* @example D | ||
* const { profile } = useSnapshot(state) | ||
* return ( | ||
* <div> | ||
* {profile.name} | ||
* </div> | ||
* ) | ||
*/ | ||
export declare const useSnapshot: <T extends object>(proxyObject: T, options?: Options | undefined) => NonPromise<T>; | ||
export {}; |
@@ -185,3 +185,3 @@ <img src="logo.svg" alt="valtio"> | ||
// the code above becomes the code blow. | ||
// the code above becomes the code below. | ||
@@ -188,0 +188,0 @@ import { useSnapshot } from 'valtio' |
@@ -5,3 +5,75 @@ import { NonPromise } from './vanilla'; | ||
}; | ||
/** | ||
* useSnapshot | ||
* | ||
* Create a local snapshot that catches changes. This hook actually returns a wrapped snapshot in a proxy for | ||
* render optimization instead of a plain object compared to `snapshot()` method. | ||
* Rule of thumb: read from snapshots, mutate the source. | ||
* The component will only re-render when the parts of the state you access have changed, it is render-optimized. | ||
* | ||
* @example A | ||
* function Counter() { | ||
* const snap = useSnapshot(state) | ||
* return ( | ||
* <div> | ||
* {snap.count} | ||
* <button onClick={() => ++state.count}>+1</button> | ||
* </div> | ||
* ) | ||
* } | ||
* | ||
* [Notes] | ||
* Every object inside your proxy also becomes a proxy (if you don't use "ref"), so you can also use them to create | ||
* the local snapshot as seen on example B. | ||
* | ||
* @example B | ||
* function ProfileName() { | ||
* const snap = useSnapshot(state.profile) | ||
* return ( | ||
* <div> | ||
* {snap.name} | ||
* </div> | ||
* ) | ||
* } | ||
* | ||
* Beware that you still can replace the child proxy with something else so it will break your snapshot. You can see | ||
* above what happens with the original proxy when you replace the child proxy. | ||
* | ||
* > console.log(state) | ||
* { profile: { name: "valtio" } } | ||
* > childState = state.profile | ||
* > console.log(childState) | ||
* { name: "valtio" } | ||
* > state.profile.name = "react" | ||
* > console.log(childState) | ||
* { name: "react" } | ||
* > state.profile = { name: "new name" } | ||
* > console.log(childState) | ||
* { name: "react" } | ||
* > console.log(state) | ||
* { profile: { name: "new name" } } | ||
* | ||
* `useSnapshot()` depends on the original reference of the child proxy so if you replace it with a new one, the component | ||
* that is subscribed to the old proxy won't receive new updates because it is still subscribed to the old one. | ||
* | ||
* In this case we recommend the example C or D. On both examples you don't need to worry with re-render, | ||
* because it is render-optimized. | ||
* | ||
* @example C | ||
* const snap = useSnapshot(state) | ||
* return ( | ||
* <div> | ||
* {snap.profile.name} | ||
* </div> | ||
* ) | ||
* | ||
* @example D | ||
* const { profile } = useSnapshot(state) | ||
* return ( | ||
* <div> | ||
* {profile.name} | ||
* </div> | ||
* ) | ||
*/ | ||
export declare const useSnapshot: <T extends object>(proxyObject: T, options?: Options | undefined) => NonPromise<T>; | ||
export {}; |
84
utils.js
@@ -8,14 +8,2 @@ 'use strict'; | ||
/** | ||
* subscribeKey | ||
* | ||
* The subscribeKey utility enables subscription to a primitive subproperty of a given state proxy. | ||
* Subscriptions created with subscribeKey will only fire when the specified property changes. | ||
* notifyInSync: same as the parameter to subscribe(); true disables batching of subscriptions. | ||
* | ||
* @example | ||
* import { subscribeKey } from 'valtio/utils' | ||
* subscribeKey(state, 'count', (v) => console.log('state.count has changed to', v)) | ||
*/ | ||
var subscribeKey = function subscribeKey(proxyObject, key, callback, notifyInSync) { | ||
@@ -31,14 +19,2 @@ var prevValue = proxyObject[key]; | ||
}; | ||
/** | ||
* devtools | ||
* | ||
* This is to connect with [Redux DevTools Extension](https://github.com/zalmoxisus/redux-devtools-extension). | ||
* Limitation: Only plain objects/values are supported. | ||
* | ||
* @example | ||
* import { devtools } from 'valtio/utils' | ||
* const state = proxy({ count: 0, text: 'hello' }) | ||
* const unsub = devtools(state, 'state name') | ||
*/ | ||
var devtools = function devtools(proxyObject, name) { | ||
@@ -94,23 +70,2 @@ var extension; | ||
}; | ||
/** | ||
* addComputed | ||
* | ||
* This adds computed values to an existing proxy object. | ||
* | ||
* [Notes] | ||
* This comes with a cost and overlaps with useSnapshot. | ||
* Do not try to optimize too early. It can worsen the performance. | ||
* Measurement and comparison will be very important. | ||
* | ||
* @example | ||
* import { proxy } from 'valtio' | ||
* import { addComputed } from 'valtio/utils' | ||
* const state = proxy({ | ||
* count: 1, | ||
* }) | ||
* addComputed(state, { | ||
* doubled: snap => snap.count * 2, | ||
* }) | ||
*/ | ||
var addComputed = function addComputed(proxyObject, computedFns, targetObject) { | ||
@@ -135,11 +90,13 @@ if (targetObject === void 0) { | ||
affected = new WeakMap(); | ||
var value = get(proxyCompare.createDeepProxy(nextSnapshot, affected)); | ||
var _value = get(proxyCompare.createDeepProxy(nextSnapshot, affected)); | ||
prevSnapshot = nextSnapshot; | ||
if (value instanceof Promise) { | ||
if (_value instanceof Promise) { | ||
pending = true; | ||
value.then(function (v) { | ||
_value.then(function (v) { | ||
targetObject[key] = v; | ||
}).catch(function (e) { | ||
// not ideal but best effort for throwing error with proxy | ||
targetObject[key] = new Proxy({}, { | ||
@@ -155,35 +112,10 @@ get: function get() { | ||
targetObject[key] = value; | ||
targetObject[key] = _value; | ||
} | ||
}; | ||
vanilla.subscribe(proxyObject, callback, true); | ||
vanilla.subscribe(proxyObject, callback); | ||
callback(); | ||
}); | ||
}; | ||
/** | ||
* proxyWithComputed | ||
* | ||
* This is to create a proxy with initial object and additional object, | ||
* which specifies getters for computed values with dependency tracking. | ||
* It also accepts optional setters for computed values. | ||
* | ||
* [Notes] | ||
* This comes with a cost and overlaps with useSnapshot. | ||
* Do not try to optimize too early. It can worsen the performance. | ||
* Measurement and comparison will be very important. | ||
* | ||
* @example | ||
* import { proxyWithComputed } from 'valtio/utils' | ||
* const state = proxyWithComputed({ | ||
* count: 1, | ||
* }, { | ||
* doubled: snap => snap.count * 2, // getter only | ||
* tripled: { | ||
* get: snap => snap.count * 3, | ||
* set: (state, newValue) => { state.count = newValue / 3 } | ||
* }, // with optional setter | ||
* }) | ||
*/ | ||
var proxyWithComputed = function proxyWithComputed(initialObject, computedFns) { | ||
@@ -190,0 +122,0 @@ Object.keys(computedFns).forEach(function (key) { |
import { createDeepProxy, isDeepChanged } from 'proxy-compare'; | ||
import { subscribe, snapshot, proxy } from './vanilla'; | ||
/** | ||
* subscribeKey | ||
* | ||
* The subscribeKey utility enables subscription to a primitive subproperty of a given state proxy. | ||
* Subscriptions created with subscribeKey will only fire when the specified property changes. | ||
* notifyInSync: same as the parameter to subscribe(); true disables batching of subscriptions. | ||
* | ||
* @example | ||
* import { subscribeKey } from 'valtio/utils' | ||
* subscribeKey(state, 'count', (v) => console.log('state.count has changed to', v)) | ||
*/ | ||
const subscribeKey = (proxyObject, key, callback, notifyInSync) => { | ||
@@ -20,3 +8,2 @@ let prevValue = proxyObject[key]; | ||
const nextValue = proxyObject[key]; | ||
if (!Object.is(prevValue, nextValue)) { | ||
@@ -27,33 +14,16 @@ callback(prevValue = nextValue); | ||
}; | ||
/** | ||
* devtools | ||
* | ||
* This is to connect with [Redux DevTools Extension](https://github.com/zalmoxisus/redux-devtools-extension). | ||
* Limitation: Only plain objects/values are supported. | ||
* | ||
* @example | ||
* import { devtools } from 'valtio/utils' | ||
* const state = proxy({ count: 0, text: 'hello' }) | ||
* const unsub = devtools(state, 'state name') | ||
*/ | ||
const devtools = (proxyObject, name) => { | ||
let extension; | ||
try { | ||
extension = window.__REDUX_DEVTOOLS_EXTENSION__; | ||
} catch (_unused) {} | ||
} catch { | ||
} | ||
if (!extension) { | ||
if (typeof process === 'object' && process.env.NODE_ENV === 'development' && typeof window !== 'undefined') { | ||
console.warn('[Warning] Please install/enable Redux devtools extension'); | ||
if (typeof process === "object" && process.env.NODE_ENV === "development" && typeof window !== "undefined") { | ||
console.warn("[Warning] Please install/enable Redux devtools extension"); | ||
} | ||
return; | ||
} | ||
let isTimeTraveling = false; | ||
const devtools = extension.connect({ | ||
name | ||
}); | ||
const devtools2 = extension.connect({name}); | ||
const unsub1 = subscribe(proxyObject, () => { | ||
@@ -63,24 +33,20 @@ if (isTimeTraveling) { | ||
} else { | ||
devtools.send(`Update - ${new Date().toLocaleString()}`, snapshot(proxyObject)); | ||
devtools2.send(`Update - ${new Date().toLocaleString()}`, snapshot(proxyObject)); | ||
} | ||
}); | ||
const unsub2 = devtools.subscribe(message => { | ||
var _message$payload3; | ||
if (message.type === 'DISPATCH' && message.state) { | ||
var _message$payload, _message$payload2; | ||
if (((_message$payload = message.payload) == null ? void 0 : _message$payload.type) === 'JUMP_TO_ACTION' || ((_message$payload2 = message.payload) == null ? void 0 : _message$payload2.type) === 'JUMP_TO_STATE') { | ||
const unsub2 = devtools2.subscribe((message) => { | ||
var _a, _b, _c; | ||
if (message.type === "DISPATCH" && message.state) { | ||
if (((_a = message.payload) == null ? void 0 : _a.type) === "JUMP_TO_ACTION" || ((_b = message.payload) == null ? void 0 : _b.type) === "JUMP_TO_STATE") { | ||
isTimeTraveling = true; | ||
} | ||
const nextValue = JSON.parse(message.state); | ||
Object.keys(nextValue).forEach(key => { | ||
Object.keys(nextValue).forEach((key) => { | ||
proxyObject[key] = nextValue[key]; | ||
}); | ||
} else if (message.type === 'DISPATCH' && ((_message$payload3 = message.payload) == null ? void 0 : _message$payload3.type) === 'COMMIT') { | ||
devtools.init(snapshot(proxyObject)); | ||
} else if (message.type === "DISPATCH" && ((_c = message.payload) == null ? void 0 : _c.type) === "COMMIT") { | ||
devtools2.init(snapshot(proxyObject)); | ||
} | ||
}); | ||
devtools.init(snapshot(proxyObject)); | ||
devtools2.init(snapshot(proxyObject)); | ||
return () => { | ||
@@ -91,29 +57,7 @@ unsub1(); | ||
}; | ||
/** | ||
* addComputed | ||
* | ||
* This adds computed values to an existing proxy object. | ||
* | ||
* [Notes] | ||
* This comes with a cost and overlaps with useSnapshot. | ||
* Do not try to optimize too early. It can worsen the performance. | ||
* Measurement and comparison will be very important. | ||
* | ||
* @example | ||
* import { proxy } from 'valtio' | ||
* import { addComputed } from 'valtio/utils' | ||
* const state = proxy({ | ||
* count: 1, | ||
* }) | ||
* addComputed(state, { | ||
* doubled: snap => snap.count * 2, | ||
* }) | ||
*/ | ||
const addComputed = (proxyObject, computedFns, targetObject = proxyObject) => { | ||
Object.keys(computedFns).forEach(key => { | ||
Object.keys(computedFns).forEach((key) => { | ||
if (Object.getOwnPropertyDescriptor(targetObject, key)) { | ||
throw new Error('object property already defined'); | ||
throw new Error("object property already defined"); | ||
} | ||
const get = computedFns[key]; | ||
@@ -123,6 +67,4 @@ let prevSnapshot; | ||
let pending = false; | ||
const callback = () => { | ||
const nextSnapshot = snapshot(proxyObject); | ||
if (!pending && (!prevSnapshot || isDeepChanged(prevSnapshot, nextSnapshot, affected))) { | ||
@@ -132,9 +74,7 @@ affected = new WeakMap(); | ||
prevSnapshot = nextSnapshot; | ||
if (value instanceof Promise) { | ||
pending = true; | ||
value.then(v => { | ||
value.then((v) => { | ||
targetObject[key] = v; | ||
}).catch(e => { | ||
// not ideal but best effort for throwing error with proxy | ||
}).catch((e) => { | ||
targetObject[key] = new Proxy({}, { | ||
@@ -144,3 +84,2 @@ get() { | ||
} | ||
}); | ||
@@ -151,49 +90,16 @@ }).finally(() => { | ||
} | ||
targetObject[key] = value; | ||
} | ||
}; | ||
subscribe(proxyObject, callback, true); | ||
subscribe(proxyObject, callback); | ||
callback(); | ||
}); | ||
}; | ||
/** | ||
* proxyWithComputed | ||
* | ||
* This is to create a proxy with initial object and additional object, | ||
* which specifies getters for computed values with dependency tracking. | ||
* It also accepts optional setters for computed values. | ||
* | ||
* [Notes] | ||
* This comes with a cost and overlaps with useSnapshot. | ||
* Do not try to optimize too early. It can worsen the performance. | ||
* Measurement and comparison will be very important. | ||
* | ||
* @example | ||
* import { proxyWithComputed } from 'valtio/utils' | ||
* const state = proxyWithComputed({ | ||
* count: 1, | ||
* }, { | ||
* doubled: snap => snap.count * 2, // getter only | ||
* tripled: { | ||
* get: snap => snap.count * 3, | ||
* set: (state, newValue) => { state.count = newValue / 3 } | ||
* }, // with optional setter | ||
* }) | ||
*/ | ||
const proxyWithComputed = (initialObject, computedFns) => { | ||
Object.keys(computedFns).forEach(key => { | ||
Object.keys(computedFns).forEach((key) => { | ||
if (Object.getOwnPropertyDescriptor(initialObject, key)) { | ||
throw new Error('object property already defined'); | ||
throw new Error("object property already defined"); | ||
} | ||
const computedFn = computedFns[key]; | ||
const { | ||
get, | ||
set | ||
} = typeof computedFn === 'function' ? { | ||
get: computedFn | ||
} : computedFn; | ||
const {get, set} = typeof computedFn === "function" ? {get: computedFn} : computedFn; | ||
let computedValue; | ||
@@ -203,6 +109,4 @@ let prevSnapshot; | ||
const desc = {}; | ||
desc.get = () => { | ||
const nextSnapshot = snapshot(proxyObject); | ||
if (!prevSnapshot || isDeepChanged(prevSnapshot, nextSnapshot, affected)) { | ||
@@ -213,10 +117,7 @@ affected = new WeakMap(); | ||
} | ||
return computedValue; | ||
}; | ||
if (set) { | ||
desc.set = newValue => set(proxyObject, newValue); | ||
desc.set = (newValue) => set(proxyObject, newValue); | ||
} | ||
Object.defineProperty(initialObject, key, desc); | ||
@@ -223,0 +124,0 @@ }); |
@@ -62,4 +62,3 @@ 'use strict'; | ||
var snapshot = Array.isArray(target) ? [] : Object.create(Object.getPrototypeOf(target)); | ||
proxyCompare.markToTrack(snapshot, true); // mark to track | ||
proxyCompare.markToTrack(snapshot, true); | ||
snapshotCache.set(receiver, { | ||
@@ -73,4 +72,3 @@ version: version, | ||
if (refSet.has(value)) { | ||
proxyCompare.markToTrack(value, false); // mark not to track | ||
proxyCompare.markToTrack(value, false); | ||
snapshot[key] = value; | ||
@@ -77,0 +75,0 @@ } else if (!isSupportedObject(value)) { |
@@ -9,9 +9,7 @@ import { getUntrackedObject, markToTrack } from 'proxy-compare'; | ||
const refSet = new WeakSet(); | ||
const ref = o => { | ||
const ref = (o) => { | ||
refSet.add(o); | ||
return o; | ||
}; | ||
const isSupportedObject = x => typeof x === 'object' && x !== null && (Array.isArray(x) || !x[Symbol.iterator]) && !(x instanceof WeakMap) && !(x instanceof WeakSet) && !(x instanceof Error) && !(x instanceof Number) && !(x instanceof Date) && !(x instanceof String) && !(x instanceof RegExp) && !(x instanceof ArrayBuffer); | ||
const isSupportedObject = (x) => typeof x === "object" && x !== null && (Array.isArray(x) || !x[Symbol.iterator]) && !(x instanceof WeakMap) && !(x instanceof WeakSet) && !(x instanceof Error) && !(x instanceof Number) && !(x instanceof Date) && !(x instanceof String) && !(x instanceof RegExp) && !(x instanceof ArrayBuffer); | ||
const proxyCache = new WeakMap(); | ||
@@ -22,68 +20,53 @@ let globalVersion = 1; | ||
if (!isSupportedObject(initialObject)) { | ||
throw new Error('unsupported object type'); | ||
throw new Error("unsupported object type"); | ||
} | ||
if (proxyCache.has(initialObject)) { | ||
return proxyCache.get(initialObject); | ||
} | ||
let version = globalVersion; | ||
const listeners = new Set(); | ||
const notifyUpdate = nextVersion => { | ||
const notifyUpdate = (nextVersion) => { | ||
if (!nextVersion) { | ||
nextVersion = ++globalVersion; | ||
} | ||
if (version !== nextVersion) { | ||
version = nextVersion; | ||
listeners.forEach(listener => listener(nextVersion)); | ||
listeners.forEach((listener) => listener(nextVersion)); | ||
} | ||
}; | ||
const createSnapshot = (target, receiver) => { | ||
const cache = snapshotCache.get(receiver); | ||
if (cache && cache.version === version) { | ||
return cache.snapshot; | ||
} | ||
const snapshot = Array.isArray(target) ? [] : Object.create(Object.getPrototypeOf(target)); | ||
markToTrack(snapshot, true); // mark to track | ||
snapshotCache.set(receiver, { | ||
version, | ||
snapshot | ||
}); | ||
Reflect.ownKeys(target).forEach(key => { | ||
const snapshot2 = Array.isArray(target) ? [] : Object.create(Object.getPrototypeOf(target)); | ||
markToTrack(snapshot2, true); | ||
snapshotCache.set(receiver, {version, snapshot: snapshot2}); | ||
Reflect.ownKeys(target).forEach((key) => { | ||
const value = target[key]; | ||
if (refSet.has(value)) { | ||
markToTrack(value, false); // mark not to track | ||
snapshot[key] = value; | ||
markToTrack(value, false); | ||
snapshot2[key] = value; | ||
} else if (!isSupportedObject(value)) { | ||
snapshot[key] = value; | ||
snapshot2[key] = value; | ||
} else if (value instanceof Promise) { | ||
if (PROMISE_RESULT in value) { | ||
snapshot[key] = value[PROMISE_RESULT]; | ||
snapshot2[key] = value[PROMISE_RESULT]; | ||
} else { | ||
const errorOrPromise = value[PROMISE_ERROR] || value; | ||
Object.defineProperty(snapshot, key, { | ||
Object.defineProperty(snapshot2, key, { | ||
get() { | ||
throw errorOrPromise; | ||
} | ||
}); | ||
} | ||
} else if (value[VERSION]) { | ||
snapshot[key] = value[SNAPSHOT]; | ||
snapshot2[key] = value[SNAPSHOT]; | ||
} else { | ||
snapshot[key] = value; | ||
snapshot2[key] = value; | ||
} | ||
}); | ||
Object.freeze(snapshot); | ||
return snapshot; | ||
Object.freeze(snapshot2); | ||
return snapshot2; | ||
}; | ||
const baseObject = Array.isArray(initialObject) ? [] : Object.create(Object.getPrototypeOf(initialObject)); | ||
@@ -95,54 +78,40 @@ const proxyObject = new Proxy(baseObject, { | ||
} | ||
if (prop === LISTENERS) { | ||
return listeners; | ||
} | ||
if (prop === SNAPSHOT) { | ||
return createSnapshot(target, receiver); | ||
} | ||
return target[prop]; | ||
}, | ||
deleteProperty(target, prop) { | ||
const prevValue = target[prop]; | ||
const childListeners = prevValue && prevValue[LISTENERS]; | ||
if (childListeners) { | ||
childListeners.delete(notifyUpdate); | ||
} | ||
const deleted = Reflect.deleteProperty(target, prop); | ||
if (deleted) { | ||
notifyUpdate(); | ||
} | ||
return deleted; | ||
}, | ||
set(target, prop, value) { | ||
var _Object$getOwnPropert; | ||
var _a; | ||
const prevValue = target[prop]; | ||
if (Object.is(prevValue, value)) { | ||
return true; | ||
} | ||
const childListeners = prevValue && prevValue[LISTENERS]; | ||
if (childListeners) { | ||
childListeners.delete(notifyUpdate); | ||
} | ||
if (refSet.has(value) || !isSupportedObject(value) || (_Object$getOwnPropert = Object.getOwnPropertyDescriptor(target, prop)) != null && _Object$getOwnPropert.set) { | ||
if (refSet.has(value) || !isSupportedObject(value) || ((_a = Object.getOwnPropertyDescriptor(target, prop)) == null ? void 0 : _a.set)) { | ||
target[prop] = value; | ||
} else if (value instanceof Promise) { | ||
target[prop] = value.then(v => { | ||
target[prop] = value.then((v) => { | ||
target[prop][PROMISE_RESULT] = v; | ||
notifyUpdate(); | ||
return v; | ||
}).catch(e => { | ||
}).catch((e) => { | ||
target[prop][PROMISE_ERROR] = e; | ||
@@ -153,3 +122,2 @@ notifyUpdate(); | ||
value = getUntrackedObject(value) || value; | ||
if (value[LISTENERS]) { | ||
@@ -160,15 +128,11 @@ target[prop] = value; | ||
} | ||
target[prop][LISTENERS].add(notifyUpdate); | ||
} | ||
notifyUpdate(); | ||
return true; | ||
} | ||
}); | ||
proxyCache.set(initialObject, proxyObject); | ||
Reflect.ownKeys(initialObject).forEach(key => { | ||
Reflect.ownKeys(initialObject).forEach((key) => { | ||
const desc = Object.getOwnPropertyDescriptor(initialObject, key); | ||
if (desc.get || desc.set) { | ||
@@ -182,17 +146,14 @@ Object.defineProperty(baseObject, key, desc); | ||
}; | ||
const getVersion = proxyObject => { | ||
if (typeof process === 'object' && process.env.NODE_ENV !== 'production' && (!proxyObject || !proxyObject[VERSION])) { | ||
throw new Error('Please use proxy object'); | ||
const getVersion = (proxyObject) => { | ||
if (typeof process === "object" && process.env.NODE_ENV !== "production" && (!proxyObject || !proxyObject[VERSION])) { | ||
throw new Error("Please use proxy object"); | ||
} | ||
return proxyObject[VERSION]; | ||
}; | ||
const subscribe = (proxyObject, callback, notifyInSync) => { | ||
if (typeof process === 'object' && process.env.NODE_ENV !== 'production' && (!proxyObject || !proxyObject[LISTENERS])) { | ||
throw new Error('Please use proxy object'); | ||
if (typeof process === "object" && process.env.NODE_ENV !== "production" && (!proxyObject || !proxyObject[LISTENERS])) { | ||
throw new Error("Please use proxy object"); | ||
} | ||
let pendingVersion = 0; | ||
const listener = nextVersion => { | ||
const listener = (nextVersion) => { | ||
if (notifyInSync) { | ||
@@ -202,3 +163,2 @@ callback(); | ||
} | ||
pendingVersion = nextVersion; | ||
@@ -211,3 +171,2 @@ Promise.resolve().then(() => { | ||
}; | ||
proxyObject[LISTENERS].add(listener); | ||
@@ -218,7 +177,6 @@ return () => { | ||
}; | ||
const snapshot = proxyObject => { | ||
if (typeof process === 'object' && process.env.NODE_ENV !== 'production' && (!proxyObject || !proxyObject[SNAPSHOT])) { | ||
throw new Error('Please use proxy object'); | ||
const snapshot = (proxyObject) => { | ||
if (typeof process === "object" && process.env.NODE_ENV !== "production" && (!proxyObject || !proxyObject[SNAPSHOT])) { | ||
throw new Error("Please use proxy object"); | ||
} | ||
return proxyObject[SNAPSHOT]; | ||
@@ -225,0 +183,0 @@ }; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
63691
1486