Skip to content

Commit 74a53a5

Browse files
committed
Make object revocable
1 parent a2b6f63 commit 74a53a5

File tree

2 files changed

+59
-12
lines changed

2 files changed

+59
-12
lines changed

package.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,21 @@
11
{
22
"name": "react-exo-hooks",
3+
"version": "1.0.0",
34
"module": "index.ts",
45
"main": "dist/index.js",
6+
"description": "A collection of useful hooks for data structures and logic, designed for efficiency",
57
"keywords": ["react", "hooks", "usearray", "use-array", "useobject", "use-object", "usemap", "use-map", "useunsaved", "use-unsaved", "useset", "use-set", "usepromise", "use-promise", "debounce"],
8+
"files": [
9+
"dist/"
10+
],
11+
"author": {
12+
"name": "exoRift",
13+
"url": "https://github.com/exoRift"
14+
},
15+
"repository": {
16+
"type": "git",
17+
"url": "git+https://github.com/exoRift/react-exo-hooks.git"
18+
},
619
"scripts": {
720
"build": "[ -d dist ] && rm -r dist; tsc --project tsconfig.build.json",
821
"lint": "eslint ."

src/hooks/use-object.ts

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,27 @@
1-
import { useCallback, useMemo, useState } from 'react'
1+
import { useCallback, useEffect, useMemo, useState } from 'react'
22

33
/**
4-
* Create an object state value that auto updates on mutation
5-
* @note Effects and memos that use this object should also listen for its signal: `+INSTANCE`
6-
* @param initial The initial object
7-
* @returns [object, setObject, forceUpdate]
4+
* Proxy an object recursively
5+
* @param object The object
6+
* @param setSignal The signal dispatch fn
7+
* @returns The proxied object
88
*/
9-
export function useObject<T extends object> (initial: T): [object: T, setObject: React.Dispatch<React.SetStateAction<T>>, forceUpdate: () => void] {
10-
const [signal, setSignal] = useState(0)
11-
const [object, setObject] = useState(initial)
9+
function proxyObject<T extends object> (object: T, setSignal: React.Dispatch<React.SetStateAction<number>>): [proxy: T, revoke: () => void] {
10+
const revocables: Array<() => void> = []
1211

13-
const proxy = useMemo(() => new Proxy(object, {
12+
const proxy = Proxy.revocable(object, {
1413
set (target, prop, newValue) {
1514
if (prop !== 'valueOf' && target[prop as keyof typeof target] !== newValue) setSignal((prior) => prior + 1)
16-
target[prop as keyof typeof target] = newValue
15+
const isPlain = (
16+
typeof object === 'object' &&
17+
(object as any) !== null &&
18+
Object.getPrototypeOf(object) === Object.prototype
19+
)
20+
if (isPlain) {
21+
const [subproxy, subrevoke] = proxyObject(newValue, setSignal)
22+
target[prop as keyof typeof target] = subproxy
23+
revocables.push(subrevoke)
24+
} else target[prop as keyof typeof target] = newValue
1725
return true
1826
},
1927

@@ -22,12 +30,38 @@ export function useObject<T extends object> (initial: T): [object: T, setObject:
2230
delete target[prop as keyof typeof target]
2331
return true
2432
}
25-
}), [object])
33+
})
34+
35+
function revoke (): void {
36+
proxy.revoke()
37+
for (const subrevoke of revocables) subrevoke()
38+
}
39+
40+
return [proxy.proxy, revoke]
41+
}
42+
43+
/**
44+
* Create an object state value that auto updates on mutation \
45+
* This hook is recursive into simple object properties. Class instances will remain unaffected
46+
* @note Effects and memos that use this object should also listen for its signal: `+INSTANCE`
47+
* @warn You should revoke the proxy if you're done with render and don't want unforseen complications. Auto-revokes on and `setObject`
48+
* @param initial The initial object
49+
* @returns [object, setObject, forceUpdate, revoke]
50+
*/
51+
export function useObject<T extends object> (initial: T): [object: T, setObject: React.Dispatch<React.SetStateAction<T>>, forceUpdate: () => void, revoke: () => void] {
52+
const [signal, setSignal] = useState(0)
53+
const [object, setObject] = useState(initial)
54+
55+
const [proxy, revoke] = useMemo(() => proxyObject(object, setSignal), [object])
2656

2757
const forceUpdate = useCallback(() => {
2858
setSignal((prior) => prior + 1)
2959
}, [])
3060

61+
useEffect(() => {
62+
return () => revoke()
63+
}, [revoke])
64+
3165
proxy.valueOf = () => signal
32-
return [proxy, setObject, forceUpdate]
66+
return [proxy, setObject, forceUpdate, revoke]
3367
}

0 commit comments

Comments
 (0)