1
+ /*
2
+ How it works:
3
+
4
+ - Users create JS proxies using the defineState() function.
5
+ - They bind this state (the proxy object) to various components via bindToStates() and bindToStateProps() functions.
6
+ - Since the proxy let's us capture changes to itself, we trigger component rerenders (on bound components) when that happens.
7
+ */
8
+
1
9
import {
2
10
ForgoRenderArgs ,
3
11
ForgoComponent ,
@@ -8,58 +16,63 @@ import {
8
16
NodeAttachedState ,
9
17
} from "forgo" ;
10
18
11
- export type ForgoProxyState = { } ;
12
-
13
- type StateMapEntry < TProps extends ForgoElementProps > = {
19
+ type StateBoundComponentInfo < TProps extends ForgoElementProps > = {
14
20
component : ForgoComponent < TProps > ;
15
21
args : ForgoRenderArgs ;
16
22
} ;
17
23
18
- type RerenderOnAnyChange < TState , TProps extends ForgoElementProps > = {
24
+ type PropertyBoundComponentInfo < TState , TProps extends ForgoElementProps > = {
19
25
propGetter : ( state : TState ) => any [ ] ;
20
- } & StateMapEntry < TProps > ;
26
+ } & StateBoundComponentInfo < TProps > ;
21
27
22
- const stateMap : Map < any , StateMapEntry < any > [ ] > = new Map ( ) ;
28
+ const stateToComponentsMap : Map < any , StateBoundComponentInfo < any > [ ] > =
29
+ new Map ( ) ;
23
30
24
31
export function defineState < TState extends Record < string , any > > (
25
32
state : TState
26
33
) : TState {
27
34
const handlers = {
28
35
set ( target : TState , prop : string & keyof TState , value : any ) {
29
- const entries = stateMap . get ( proxy ) ;
36
+ const entries = stateToComponentsMap . get ( proxy ) ;
30
37
31
38
// if bound to the state directly, add for updation on any state change.
32
- const argsForUncheckedUpdation : ForgoRenderArgs [ ] = entries
39
+ const stateBoundComponentArgs : ForgoRenderArgs [ ] = entries
33
40
? entries
34
- . filter ( ( x ) => ! ( x as RerenderOnAnyChange < TState , any > ) . propGetter )
41
+ . filter (
42
+ ( x ) => ! ( x as PropertyBoundComponentInfo < TState , any > ) . propGetter
43
+ )
35
44
. map ( ( x ) => x . args )
36
45
: [ ] ;
37
46
38
- // Get the props before update
39
- let propsToCompare = entries
40
- ? entries
41
- . filter ( ( x ) => ( x as RerenderOnAnyChange < TState , any > ) . propGetter )
42
- . map ( ( x ) => ( {
43
- args : x . args ,
44
- props : ( x as RerenderOnAnyChange < TState , any > ) . propGetter ( target ) ,
45
- } ) )
47
+ const propBoundComponents = entries
48
+ ? entries . filter (
49
+ ( x ) => ( x as PropertyBoundComponentInfo < TState , any > ) . propGetter
50
+ )
46
51
: [ ] ;
47
52
53
+ // Get the props before update
54
+ let propBoundComponentArgs = propBoundComponents . map ( ( x ) => ( {
55
+ args : x . args ,
56
+ props : ( x as PropertyBoundComponentInfo < TState , any > ) . propGetter (
57
+ target
58
+ ) ,
59
+ } ) ) ;
60
+
48
61
target [ prop ] = value ;
49
62
50
63
// Get the props after update
51
- let updatedProps = entries
52
- ? entries
53
- . filter ( ( x ) => ( x as RerenderOnAnyChange < TState , any > ) . propGetter )
54
- . map ( ( x ) => ( {
55
- args : x . args ,
56
- props : ( x as RerenderOnAnyChange < TState , any > ) . propGetter ( target ) ,
57
- } ) )
58
- : [ ] ;
59
-
60
- // concat state based updates and props based updates
61
- const argsListToUpdate = argsForUncheckedUpdation . concat (
62
- propsToCompare
64
+ let updatedProps = propBoundComponents . map ( ( x ) => ( {
65
+ args : x . args ,
66
+ props : ( x as PropertyBoundComponentInfo < TState , any > ) . propGetter (
67
+ target
68
+ ) ,
69
+ } ) ) ;
70
+
71
+ // State bound components (a) need to be rerendered anyway.
72
+ // Prop bound components (b) are rendendered if changed.
73
+ // So concat (a) and (b)
74
+ const argsListToUpdate = stateBoundComponentArgs . concat (
75
+ propBoundComponentArgs
63
76
. filter ( ( oldProp , i ) =>
64
77
oldProp . props . some ( ( p , j ) => p !== updatedProps [ i ] . props [ j ] )
65
78
)
@@ -84,7 +97,7 @@ export function defineState<TState extends Record<string, any>>(
84
97
} ) ;
85
98
86
99
// If a parent component is already rerendering,
87
- // don't queue the child rerender.
100
+ // don't queue the child rerender.
88
101
const componentsToUpdate = componentStatesAndArgs . filter ( ( item ) => {
89
102
const [ componentState , args ] = item ;
90
103
@@ -141,7 +154,7 @@ let argsToRenderInTheNextCycle: ForgoRenderArgs[] = [];
141
154
function doRender ( ) {
142
155
if ( argsToRenderInTheNextCycle . length ) {
143
156
for ( const args of argsToRenderInTheNextCycle ) {
144
- if ( args . element . node ) {
157
+ if ( args . element . node && args . element . node . isConnected ) {
145
158
rerender ( args . element ) ;
146
159
}
147
160
}
@@ -167,23 +180,23 @@ export function bindToStateProps<TState, TProps extends ForgoElementProps>(
167
180
...component ,
168
181
mount ( props : TProps , args : ForgoRenderArgs ) {
169
182
for ( const [ state , propGetter ] of stateBindings ) {
170
- let entries = stateMap . get ( state ) ;
183
+ let entries = stateToComponentsMap . get ( state ) ;
171
184
172
185
if ( ! entries ) {
173
186
entries = [ ] ;
174
- stateMap . set ( state , entries ) ;
187
+ stateToComponentsMap . set ( state , entries ) ;
175
188
}
176
189
177
190
if ( propGetter ) {
178
- const newEntry : RerenderOnAnyChange < TState , TProps > = {
191
+ const newEntry : PropertyBoundComponentInfo < TState , TProps > = {
179
192
component : wrappedComponent ,
180
193
propGetter,
181
194
args,
182
195
} ;
183
196
184
197
entries . push ( newEntry ) ;
185
198
} else {
186
- const newEntry : StateMapEntry < TProps > = {
199
+ const newEntry : StateBoundComponentInfo < TProps > = {
187
200
component : wrappedComponent ,
188
201
args,
189
202
} ;
@@ -198,10 +211,10 @@ export function bindToStateProps<TState, TProps extends ForgoElementProps>(
198
211
} ,
199
212
unmount ( props : TProps , args : ForgoRenderArgs ) {
200
213
for ( const [ state ] of stateBindings ) {
201
- let entry = stateMap . get ( state ) ;
214
+ let entry = stateToComponentsMap . get ( state ) ;
202
215
203
216
if ( entry ) {
204
- stateMap . set (
217
+ stateToComponentsMap . set (
205
218
state ,
206
219
entry . filter ( ( x ) => x . component !== wrappedComponent )
207
220
) ;
0 commit comments