This is Part 2 of 3 in this tutorial series about Normalized Reducer. In Part 1, I explained the usefulness of normalized data and introduced Normalized Reducer. Then we built the first part of our bookmarks app by using Normalized Reducer to implement creation for profile entities. Here in Part 2, we will implement deleting, updating, and moving/reordering.
Entity Deletion
We will implement deletion via
actionCreators.delete
. Make a Profile
component that looks like:import IconButton from '@material-ui/core/IconButton'; import DeleteIcon from '@material-ui/icons/Delete'; // ... function Profile({ id }) { const { dispatch } = useContext(ModelContext); const handleDelete = () => { dispatch(actionCreators.delete('profile', id)); } return ( <div style={styles.profile}> Profile ID: {id} <IconButton onClick={handleDelete} color="secondary"><DeleteIcon/></IconButton> </div> ); }
In the
Profiles
component (from Part 1) replace {id}
with <Profile id={id}/>
:function Profiles() { // // ... // return ( <div> <Button onClick={createProfile} color="primary">New Profile</Button> <div style={styles.profilesInner}> {ids.map(id => ( <Card key={id} style={styles.card}> <Profile id={id}/> </Card>))} </div> </div> ); }
Save and reload the page. You should now be able to create and delete the items. If you
console.log
the state, when you delete a profile, you should see its data object vanish from the entities.profile
hashmap and its ID vanish from the ids.profile
array.Entity Update
Now let’s display some profile attributes using
selectors.getEntity
, and implement attribute updates using actionCreators.update
. Add these to the Profile
component:function Profile({ id, index }) { const { state, dispatch } = useContext(ModelContext); const profile = selectors.getEntity(state, { type: 'profile', id }); if (!profile) { return null; } const handleChangeName = e => { dispatch(actionCreators.update('profile', id, { name: e.target.value })) } const handleDelete = () => { dispatch(actionCreators.delete('profile', id)); } return ( <div style={styles.profile}> <div style={styles.profileName}> <TextFieldautoFocus fullWidth placeholder={`Profile: (ID ${id})`} value={profile.name || ''} onChange={handleChangeName}/> </div> <IconButton onClick={handleDelete} color="secondary"><DeleteIcon/></IconButton> </div> ); }
Save and reload the app. Get some profiles showing, and you should see text inputs for each. If you
console.log
the state, you should see the profile’s data change as you type.Entity Move
Next, let’s allow the user to change the order of the profiles. On each, we will add an up-button that decrements the profile’s position in the collection, and a down-button that increments it. We will use
actionCreators.move
to dispatch the action.function Profile({ id, index }) { const { state, dispatch } = useContext(ModelContext); const profile = selectors.getEntity(state, { type: 'profile', id }); if (!profile) { return null; } const handleChangeName = e => { dispatch(actionCreators.update('profile', id, { name: e.target.value })) } const handleMoveUp = () => { dispatch(actionCreators.move('profile', index, index - 1)); } const handleMoveDown = () => { dispatch(actionCreators.move('profile', index, index + 1)); } // // ... // return ( <div style={styles.profile}> <div style={styles.profileMoveButtons}> <IconButton onClick={handleMoveUp}><UpIcon/></IconButton> <IconButton onClick={handleMoveDown}><DownIcon/></IconButton> </div> // ... </div> ); }
Not too different from the other actions. Save and reload the app, and try it out. Once again,
console.log
the state, and when you move an entity, you will see its ID in ids.profile
get reindexed.Part 2 is now complete, with the final product of Part 2 shown here:
In Part 3, we will implement a one-to-many relationship by allowing a user to manage bookmarks of a profile.