@@ -29,8 +29,7 @@ const config = {
29
29
// identity is a special unique attribute for user generated ids
30
30
// E.g. todoFilters are settings that should be easy to lookup by their identity
31
31
identity : 'todoFilters' ,
32
- showCompleted : true ,
33
- project : 0
32
+ showCompleted : true
34
33
}
35
34
} , {
36
35
user : {
@@ -110,57 +109,6 @@ const NewTodo = () => {
110
109
)
111
110
}
112
111
113
- const TodoFilters = ( ) => {
114
- const [ filters ] = useEntity ( { identity : 'todoFilters' } )
115
- const [ transact ] = useTransact ( )
116
- return (
117
- < div >
118
- < label htmlFor = "show-completed" > Show Completed?</ label >
119
- < input
120
- type = "checkbox"
121
- id = "show-completed"
122
- checked = { filters . get ( 'showCompleted' ) }
123
- onChange = { e => transact ( [ { todoFilter : { id : filters . get ( 'id' ) , showCompleted : e . target . checked } } ] ) }
124
- />
125
- ·
126
- < ProjectSelect
127
- value = { filters . get ( 'project' ) }
128
- onChange = { project => transact ( [ { todoFilter : { id : filters . get ( 'id' ) , project } } ] ) }
129
- />
130
- </ div >
131
- )
132
- }
133
-
134
- const ProjectSelect = ( { value, onChange } ) => {
135
- const [ projects ] = useQuery ( {
136
- $find : 'project' ,
137
- $where : { project : { name : '$any' } }
138
- } )
139
- return (
140
- < >
141
- < label >
142
- Project:
143
- </ label >
144
-
145
- < select
146
- name = "projects"
147
- value = { value }
148
- onChange = { e => onChange && onChange ( Number ( e . target . value ) ) }
149
- >
150
- < option value = "0" > </ option >
151
- { projects . map ( project => (
152
- < option
153
- key = { project . get ( 'id' ) }
154
- value = { project . get ( 'id' ) }
155
- >
156
- { project . get ( 'name' ) }
157
- </ option >
158
- ) ) }
159
- </ select >
160
- </ >
161
- )
162
- }
163
-
164
112
const TodoList = ( ) => {
165
113
const [ filters ] = useEntity ( { identity : 'todoFilters' } )
166
114
const [ todos ] = useQuery ( {
@@ -169,36 +117,42 @@ const TodoList = () => {
169
117
} )
170
118
return (
171
119
< div >
172
- { todos
173
- . filter ( todo => {
120
+ { todos . filter ( todo => {
174
121
if ( ! filters . get ( 'showCompleted' ) && todo . get ( 'isCompleted' ) ) return false
175
122
if ( filters . get ( 'project' ) && todo . get ( 'project' , 'id' ) !== filters . get ( 'project' ) ) return false
123
+ if ( filters . get ( 'owner' ) && todo . get ( 'owner' , 'id' ) !== filters . get ( 'owner' ) ) return false
176
124
return true
177
- } )
178
- . sort ( ( a , b ) => a . get ( 'createdAt' ) > b . get ( 'createdAt' ) ? - 1 : 1 )
179
- . map ( todo => < Todo key = { todo . get ( 'id' ) } todo = { todo } /> ) }
125
+ } ) . sort ( ( a , b ) => a . get ( 'createdAt' ) > b . get ( 'createdAt' ) ? - 1 : 1 )
126
+ . map ( todo => < Todo key = { todo . get ( 'id' ) } id = { todo . get ( 'id' ) } /> ) }
180
127
</ div >
181
128
)
182
129
}
183
130
184
- const Todo = ( { todo } ) => (
185
- < div >
186
- < div style = { { display : 'flex' , flexDirection : 'row' , alignItems : 'flex-end' , paddingTop : 20 } } >
187
- < TodoCheck todo = { todo } />
188
- < TodoName todo = { todo } />
189
- </ div >
131
+ // PERFORMANCE: By accepting an `id` prop instead of a whole `todo` entity
132
+ // this component stays disconnected from the useQuery in the parent TodoList.
133
+ // useEntity creates a separate scope for every Todo so changes to TodoList
134
+ // or sibling Todos don't trigger unnecessary re-renders.
135
+ const Todo = React . memo ( ( { id } ) => {
136
+ const [ todo ] = useEntity ( id )
137
+ return (
190
138
< div >
191
- < TodoProject todo = { todo } />
192
- ·
193
- < TodoOwner todo = { todo } />
194
- ·
195
- < TodoDelete todo = { todo } />
139
+ < div style = { { display : 'flex' , flexDirection : 'row' , alignItems : 'flex-end' , paddingTop : 20 } } >
140
+ < TodoCheck todo = { todo } />
141
+ < TodoName todo = { todo } />
142
+ </ div >
143
+ < div >
144
+ < TodoProject todo = { todo } />
145
+ ·
146
+ < TodoOwner todo = { todo } />
147
+ ·
148
+ < TodoDelete todo = { todo } />
149
+ </ div >
150
+ < small style = { { color : 'grey' } } >
151
+ { todo . get ( 'createdAt' ) . toLocaleString ( ) }
152
+ </ small >
196
153
</ div >
197
- < small style = { { color : 'grey' } } >
198
- { todo . get ( 'createdAt' ) . toLocaleString ( ) }
199
- </ small >
200
- </ div >
201
- )
154
+ )
155
+ } )
202
156
203
157
const TodoCheck = ( { todo } ) => {
204
158
const [ transact ] = useTransact ( )
@@ -207,12 +161,7 @@ const TodoCheck = ({ todo }) => {
207
161
type = "checkbox"
208
162
style = { { width : 20 , height : 20 , cursor : 'pointer' } }
209
163
checked = { ! ! todo . get ( 'isCompleted' ) }
210
- onChange = { e => transact ( [ {
211
- todo : {
212
- id : todo . get ( 'id' ) ,
213
- isCompleted : e . target . checked
214
- }
215
- } ] ) }
164
+ onChange = { e => transact ( [ { todo : { id : todo . get ( 'id' ) , isCompleted : e . target . checked } } ] ) }
216
165
/>
217
166
)
218
167
}
@@ -222,10 +171,10 @@ const TodoName = ({ todo }) => {
222
171
return (
223
172
< input
224
173
style = { {
225
- border : 'none' , fontSize : 20 , marginTop : - 2 , cursor : 'pointer' ,
174
+ border : 'none' , fontSize : 20 , marginTop : - 2 , cursor : 'pointer' ,
226
175
...todo . get ( 'isCompleted' ) && { textDecoration : 'line-through ' }
227
176
} }
228
- value = { todo . get ( 'name' ) }
177
+ defaultValue = { todo . get ( 'name' ) }
229
178
onChange = { e => transact ( [ { todo : { id : todo . get ( 'id' ) , name : e . target . value } } ] ) }
230
179
/>
231
180
)
@@ -234,41 +183,24 @@ const TodoName = ({ todo }) => {
234
183
const TodoProject = ( { todo } ) => {
235
184
const [ transact ] = useTransact ( )
236
185
return (
237
- < ProjectSelect
238
- value = { todo . get ( 'project' , 'id' ) || '' }
239
- onChange = { projectId => transact ( [ { todo : { id : todo . get ( 'id' ) , 'project' : projectId || null } } ] ) }
186
+ < EntitySelect
187
+ label = "Project"
188
+ entityType = "project"
189
+ value = { todo . get ( 'project' , 'id' ) }
190
+ onChange = { project => transact ( [ { todo : { id : todo . get ( 'id' ) , project } } ] ) }
240
191
/>
241
192
)
242
193
}
243
194
244
195
const TodoOwner = ( { todo } ) => {
245
196
const [ transact ] = useTransact ( )
246
- const [ users ] = useQuery ( {
247
- $find : 'user' ,
248
- $where : { user : { name : '$any' } }
249
- } )
250
197
return (
251
- < >
252
- < label >
253
- Owner:
254
- </ label >
255
-
256
- < select
257
- name = "users"
258
- value = { todo . get ( 'owner' , 'id' ) || '' }
259
- onChange = { e => transact ( [ { todo : { id : todo . get ( 'id' ) , owner : Number ( e . target . value ) || null } } ] ) }
260
- >
261
- < option value = "" > </ option >
262
- { users . map ( user => (
263
- < option
264
- key = { user . get ( 'id' ) }
265
- value = { user . get ( 'id' ) }
266
- >
267
- { user . get ( 'name' ) }
268
- </ option >
269
- ) ) }
270
- </ select >
271
- </ >
198
+ < EntitySelect
199
+ label = "Owner"
200
+ entityType = "user"
201
+ value = { todo . get ( 'owner' , 'id' ) }
202
+ onChange = { owner => transact ( [ { todo : { id : todo . get ( 'id' ) , owner } } ] ) }
203
+ />
272
204
)
273
205
}
274
206
@@ -279,4 +211,57 @@ const TodoDelete = ({ todo }) => {
279
211
Delete
280
212
</ button >
281
213
)
282
- }
214
+ }
215
+
216
+ const TodoFilters = ( ) => {
217
+ const [ filters ] = useEntity ( { identity : 'todoFilters' } )
218
+ const [ transact ] = useTransact ( )
219
+ return (
220
+ < div >
221
+ < label > Show Completed?
222
+ < input
223
+ type = "checkbox"
224
+ checked = { filters . get ( 'showCompleted' ) }
225
+ onChange = { e => transact ( [ { todoFilter : { id : filters . get ( 'id' ) , showCompleted : e . target . checked } } ] ) }
226
+ />
227
+ </ label >
228
+ ·
229
+ < EntitySelect
230
+ label = "Project"
231
+ entityType = "project"
232
+ value = { filters . get ( 'project' ) }
233
+ onChange = { project => transact ( [ { todoFilter : { id : filters . get ( 'id' ) , project } } ] ) }
234
+ />
235
+ ·
236
+ < EntitySelect
237
+ label = "Owner"
238
+ entityType = "user"
239
+ value = { filters . get ( 'owner' ) }
240
+ onChange = { owner => transact ( [ { todoFilter : { id : filters . get ( 'id' ) , owner } } ] ) }
241
+ />
242
+ </ div >
243
+ )
244
+ }
245
+
246
+ const EntitySelect = React . memo ( ( { label, entityType, value, onChange } ) => {
247
+ const [ entities ] = useQuery ( {
248
+ $find : entityType ,
249
+ $where : { [ entityType ] : { name : '$any' } }
250
+ } )
251
+ return (
252
+ < label > { label } :
253
+ < select
254
+ name = { entityType }
255
+ value = { value || '' }
256
+ onChange = { e => onChange && onChange ( Number ( e . target . value ) || null ) }
257
+ >
258
+ < option key = "-" value = "" > </ option >
259
+ { entities . map ( entity => (
260
+ < option key = { entity . get ( 'id' ) } value = { entity . get ( 'id' ) } >
261
+ { entity . get ( 'name' ) }
262
+ </ option >
263
+ ) ) }
264
+ </ select >
265
+ </ label >
266
+ )
267
+ } )
0 commit comments