Adding Memories

Adding a new memory could be painful. When the user hits the new button, you could create a local-only memory in the global store. Since your app is designed around each memory having a unique ID, you'd have to come up with an ID that didn't conflict with any of the remote memories. Maybe you could use -1 or 0 since SQL doesn't assign either as an ID. But then what would happen if the user clicked the new button twice? When the user clicks the save button, you'd need to decide whether the memory was a new one that needed to inserted or an old one that needed to updated.

These challenges aren't impossible to overcome, but there's a cleaner way. When the user hits the new button, you immediately add a blank memory to the database. Only when you've received the ID of this new memory do you add it to the store. This approach requires a thunk that fires off a POST request to the insert endpoint of your web service, so you define a new thunk creator:

export function newMemory(year, month, day) {
const memory = {
year,
month,
day,
entry: '',
};

return dispatch => {
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(memory),
};
fetch(`https://example.com:8443/memories`, options)
.then(assertResponse)
.then(response => response.json())
.then(data => {
if (data.ok) {
dispatch(addMemory({
...memory,
id: data.results,
isEditing: true,
}));
}
});
};
}

The thunk creator expects the year, month, and day to be given as parameters. The entry is blank. If the memory is successfully added to the database, then you issue an action to push the new memory into the global store. You assign the memory the ID that the web service sent back in its response. Also, you set the memory up for editing.

You need an action to add the new memory to the store, so you create an enum instance and an action creator:

export const Action = Object.freeze({
// ...
AddMemory: 'AddMemory',
});

export function addMemory(memory) {
return {type: Action.AddMemory, payload: memory};
}

You have created a new action, so you immediately turn to the reducer to add a case that responds to this action. You don't have to update an existing memory. Instead, you build up a new array of memories built off the old memories and the new one. You put the new one at the front of the array so that it appears at the top of the page:

function reducer(state, action) {
switch (action.type) {
// ...
case Action.AddMemory:
return {
...state,
memories: [action.payload, ...state.memories],
};
// ...
}
}

One last step before memories are in the making. You dispatch the new memory thunk when the user clicks the new button in the toolbar:

import {fetchDay, newMemory} from './actions';

export function Toolbar() {
// ...

return (
<div className="toolbar">
<button
onClick={() => dispatch(newMemory(today.getFullYear(), month, day))}
>New Memory</button>
{/* ... */}
</div>
);
}

Try clicking on the new button. Does a new memory appear? Can you edit and save it? When you reload the app, is it still there?

The app is nearly functional. But you've probably added some memories while testing that you don't really want in the database. You wish you could delete them from within the client.