Saving Edits

When the user saves edits to a memory, you want the changes to appear in two places: the global Redux store and the web service. You also want the Memory component to switch back to viewing mode. But this shouldn't happen immediately. If the request to the web service fails, you want to give the user a chance to hit the save button again.

To test out saving, you need to be working with memories that are in your database. You restore the useEffect call in the App component and ensure that you have at least one memory for the current day in the database.

The app's response to a save depends on a succesful fetch, so you start with a thunk creator that sends the memory up to the PATCH endpoint of your service:

export function saveMemoryEdit(memory) {
return dispatch => {
const options = {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(memory),
};
fetch(`https://example.com:8443/memories/${memory.id}`, options)
.then(assertResponse)
.then(response => response.json())
.then(data => {
if (data.ok) {
dispatch(replaceMemory({...memory, isEditing: false}));
} else {
console.error(data);
}
});
};
}

On a successful update, the thunk dispatches an action to replace the edited memory in the global store. The new memory is passed as a parameter to the thunk creator. When the memory is sent along to the replace action, its isEdited flag is set to false, which will switch the memory back to viewing mode.

You add an enum instance and an action creator:

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

export function replaceMemory(memory) {
return {type: Action.ReplaceMemory, payload: memory};
}

If the update isn't successful, you should really display an error message to the user. Later, you tell yourself.

Whenever you add a new action, you reflexively turn to your reducer and add a case to handle it. For this action, you need to build a new array of memories like the old one, but with the updated memory in place of the old version. The map function again proves useful:

function reducer(state, action) {
switch (action.type) {
// ...
case Action.ReplaceMemory:
return {
...state,
memories: state.memories.map(memory => {
if (memory.id === action.payload.id) {
return action.payload;
} else {
return memory;
}
}),
};
// ...
}
}

In the Memory component, you dispatch the save thunk when the user clicks the save button:

// ...
import {startMemoryEdit, cancelMemoryEdit, saveMemoryEdit} from './actions';

export function Memory(props) {
// ...

if (memory.isEditing) {
return (
// ...
<button
onClick={() => dispatch(saveMemoryEdit({...memory, entry}))}
>Save</button>
// ...
);
} else {
// ...
}
}

The memory that's sent to the thunk creator is built out of the original date, the original ID, and the component's local state for the entry.

Try editing a memory from your database. Does it switch back to viewing mode? When you reload the app, do you see that the changes were persisted in the database?

Testing the app would be much easier if you could add new memories. It's time to add that feature.