Table of contents
I've managed to turn this article into a series β hope you've enjoyed it so far π.
In the last part, we cleaned up the code by creating components, we added a delete
button to delete each contact from our display and from the backend, we also added a search
feature.
In this article, we are going to add a confirmation window to the delete
feature, we are also going to work on updating contacts β in this case, replacing the number for a name that already exists on the phonebook with a new number.
I strongly advise going through the last part of this article before jumping on this.
Let's get started π
Adding a Delete Prompt
Right now on our app when a contact gets deleted, it goes away forever without any confirmation. We want to allow users to confirm if they want a contact to be deleted.
To accomplish this, we use window.confirm()
method.
In handleContactDelete
we add the window.confirm()
method above the axios.delete
method, we need to get access to the name of the contact being deleted so we pass in a parameter to the handleContactDelete
function, let's call it item
.
Using template literal, our code would look like this
const handleContactDelete = (id, item) => {
window.confirm(`Delete ${item} from phonebook?`)
axios.delete(`http://localhost:3001/persons/${id}`).then(() => {
setPersons(
persons.filter((l) => {
return l.id !== id;
})
);
});
};
Now we need to pass in person.name
as an argument to the handleContactDelete
function reference found in App.js
handleDelete={() => handleContactDelete(person.id, person.name)}
When we try to delete a contact, a window pops up asking if we want to delete β clicking okay deletes the contact, clicking cancel also deletes the contact.
To fix this, we use a ternary expression.
const handleContactDelete = (id, item) => {
window.confirm(`Delete ${item} from phonebook ?`)
? axios.delete(`http://localhost:3001/persons/${id}`).then(() => {
setPersons(
persons.filter((l) => {
return l.id !== id;
})
);
})
: null;
};
This simply means when the confirmation pops up and okay is clicked the contact gets deleted else do nothing. If you have eslint
extension installed on vscode it would show some red error lines, you can place this // eslint-disable-next-line no-unused-expressions
above window.confirm
to remove the error lines.
Now when we try to delete, it asks for confirmation β we can delete or cancel the action if we want.
Updating a contact
We want to change the functionality to update new numbers ie if a number is already added to an existing user, the new number will replace the old number, the app will also confirm the contact already has a number using window.confirm()
.
In the first part of this series, we used alert()
to show that there was an existing name or number.
The old logic was if newName
is found in the nameCheck
array or newNumber
is found in numCheck
an alert pops up saying contact already exists β if that's not the case, the name
and number
are added to the persons
array and displayed on the contact list.
const nameCheck = persons.map((person) => person.name);
const numCheck = persons.map((person) => person.number);
nameCheck.includes(newName) || numCheck.includes(newNumber)
? alert(`Contact already exists, Change it`)
: axios
.post('http://localhost:3001/persons', addPerson)
.then((response) => {
setPersons(persons.concat(addPerson));
setName('');
setNumber('');
});
};
We remove the numCheck
part since we want to replace old numbers with new ones.
To achieve this replacement, we use chained ternary expression β I'll explain so please stay with me.
The new logic is if newName
is found in the nameCheck
array, a window confirmation pops up asking if we want to replace the number. On confirmation, we use the axios.put
method to pass in a new object containing the new number and replace the old number on the backend.
If we cancel confirmation, no changes happen. β if newName
is not included in the nameCheck
array, we add the name
and number
as normal using axios.post
and update the persons
array using setPersons
.
It's not as complicated as it seems, so I'll break it down further β first, we remove alert()
and add window.confirm()
nameCheck.includes(newName)
? window.confirm(`${newName} already exist, replace old number with new one?`)
: axios
.post('http://localhost:3001/persons', addPerson)
.then((response) => {
setPersons(persons.concat(addPerson));
setName('');
setNumber('');
});
};
Now we need to chain the expression so that it does something when we click on the window.confirm()
prompt β for now, we log a string to the console
nameCheck.includes(newName)
? window.confirm(
`${newName} already exist, replace old number with new one?`
)
? console.log('it works')
: null
: axios.post('http://localhost:3001/persons', addPerson).then(() => {
setPersons(persons.concat(addPerson));
setName('');
setNumber('');
});
};
When we click on okay to confirm the replacement of number, we see it works
logged on our console, and when we cancel nothing happens which is what we want. Awesome! now we replace console.log()
with axios.put()
method which is used to update data on the backend β axios.put()
would require a new object passed to it, we would create that β we would also need to access the id
of each contact we want to be replaced.
const addNewNumber = {
name: newName,
number: newNumber
}
There is no need to add id
to the above object since the backend generates an id
for us, we can also remove the id
we added onaddPerson
object.
We need to retrieve individual id
for each contact to add to the API URL that's needed by axios.put()
. This was tricky for me to accomplish because there was no way I could access person.id
and I couldn't pass down the id
as a parameter through the handleSubmit
function. I had to bring out a paper and pen and drew out what I wanted to accomplish before this method came to mind β If you are reading this and there is an easier way to accomplish please drop a comment.
So what I did was map
through the persons
array and return the id
of names that matched with newName
and returned null if they didn't match, then I filtered the nulls so I could have just the id
const id = persons
.map((person) => (person.name === newName ? person.id : null))
.filter((n) => n != null);
Now we have an id
variable that matches with the particular contact we want to replace. We use this with the API URL.
Remember to use // eslint-disable-next-line no-unused-expressions
above windows.confirm()
to remove eslint
error lines on Vscode.
nameCheck.includes(newName)
? window.confirm(
`${newName} already exist, replace old number with new one?`
)
? axios
.put(`http://localhost:3001/persons/${id}`, addNewNumber)
.then((response) =>
setPersons(
persons.map((person) =>
person.name !== newName ? person : response.data
)
)
)
: null
: axios.post('http://localhost:3001/persons', addPerson).then(() => {
setPersons(persons.concat(addPerson));
setName('');
setNumber('');
});
};
To update the persons
state, we simply map through the persons
array and return our new array only when the newName matches person.name
.
I tried to just concat
directly here, that is setPersons(persons.concat(addNewNumber)
it updated at the backend but did not re-render the page the changes only took effect when I manually refreshed the page. The map method creates a new array that only returns when the newName
is the same as any name
on the old array, if this condition isn't met person
is just returned.
Testing our app, we can see that the contacts update successfully.
Extracting into modules
We separate our backend from the main code to make our code cleaner and readable.
In src
folder, we create a folder called services
and create a file called persons.js
In persons.js
, we import axios
and save the API URL in a variable called url
import axios from 'axios';
const url = 'http://localhost:3001/persons';
Now we create the different functions for all our methods(post, put, get, delete) and export them.
const getAll = () => {
const request = axios.get(url);
return request.then((response) => response.data);
};
const create = (obj) => {
const request = axios.post(url, obj);
return request.then((response) => response.data);
};
const deleteObj = (id) => {
const request = axios.delete(`${url}/${id}`);
return request.then((response) => response.data);
};
const replaceNum = (id, obj) => {
const request = axios.put(`${url}/${id}`, obj);
return request.then((response) => response.data);
};
export default { getAll, create, deleteObj, replaceNum };
In App.js
we import personService
from the service folder and remove the axios
import because we don't need it anymore
import personService from './services/persons';
Then we replace
useEffect(() => {
axios.get('http://localhost:3001/persons').then((response) => {
setPersons(response.data);
});
}, []);
nameCheck.includes(newName)
? window.confirm(
`${newName} already exist, replace old number with new one?`
)
? axios
.put(`http://localhost:3001/persons/${id}`, addNewNumber)
.then((response) =>
setPersons(
persons.map((person) =>
person.name !== newName ? person : response.data
)
)
)
: null
: axios.post('http://localhost:3001/persons', addPerson).then(() => {
setPersons(persons.concat(addPerson));
setName('');
setNumber('');
});
};
const handleContactDelete = (id, item) => {
// eslint-disable-next-line no-unused-expressions
window.confirm(`Delete ${item} from phonebook ?`)
? axios.delete(`http://localhost:3001/persons/${id}`).then(() => {
setPersons(
persons.filter((l) => {
return l.id !== id;
})
);
})
: null;
};
with
useEffect(() => {
personService.getAll().then((initialData) => {
setPersons(initialData);
});
}, []);
nameCheck.includes(newName)
? window.confirm(
`${newName} already exist, replace old number with new one?`
)
? personService
.replaceNum(id, addNewNumber)
.then((numNew) =>
setPersons(
persons.map((person) =>
person.name !== newName ? person : numNew
)
)
)
: null
: personService.create(addPerson).then(() => {
setPersons(persons.concat(addPerson));
setName('');
setNumber('');
});
};
const handleContactDelete = (id, item) => {
// eslint-disable-next-line no-unused-expressions
window.confirm(`Delete ${item} from phonebook ?`)
? personService.deleteObj(id).then(() => {
setPersons(
persons.filter((l) => {
return l.id !== id;
})
);
})
: null;
};
This cleans up the app.js and also keeps the backend in its module. Promise chaining was used to achieve this and you can read up more on it here.
Our app still works fine, and there are no errors on the console.
If you've heard of CRUD
operations, that's basically what we've achieved in these series β We created, read, updated, and deleted data from our app and the backend.
Now we can apply some styling to bring life to it π
Thanks for reading and I hope you learned something. If there are simpler ways to achieve these please comment and as usual pardon all grammatical errors π