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