React: Working with the server(Part 2)

Β·

7 min read

I started writing about working with the server, it was supposed to be just one article but here we are πŸ˜ƒ

In the first part of this article we created a fake backend database using JSON SERVER β€” we created a phonebook app that lets us add new contacts and also updates the backend database.

In this part, we are going to work through deleting a contact, and building a search feature.

Creating Components

The App.js file is starting to look a lot clustered so before we jump into deleting and adding other features, let's refactor the code to make it cleaner by creating components.

In src folder, create a folder called components and a file called Header.js β€” remember that components' names should always start with caps.

In Header.js we import react and create the Header component.

Using destructuring, this component would be accepting a text props that would be passed from App.js

import React from 'react';

const Header = ({ text }) => {
  return (
    <>
      <h3>{text}</h3>
    </>
  );
};

export default Header;

We import the Header component in App.js and replace the appropriate headers passing the value for the text props

import Header from './components/Header';

 <Header text='Add New Contact'/>
 <Header text='Contact List'/>

Now, we create another component for form called AddContact.js

In AddContact.js, we get the different props needed for the inputs, onSubmit, and onChange, and pass them to the appropriate elements that need them. Since the onChange and value are needed by two different input fields, we give them befitting names to easily differentiate them.

import React from 'react';

const AddContact = ({
  onSubmit,
  onChangeName,
  onChangeNum,
  valueName,
  valueNum,
  type,
}) => {
  return (
    <>
      <form onSubmit={onSubmit}>
        <div>
          name: <input type={type} onChange={onChangeName} value={valueName} />
        </div>
        <div>
          number: <input type={type} onChange={onChangeNum} value={valueNum} />
        </div>
        <button>ADD NEW</button>
      </form>
    </>
  );
};
export default AddContact;

In App.js, we replace the form element with our AddContact component.

import AddContact from './components/AddContact';

  return (
    <>
      <h2>PHONEBOOK</h2>
      <Header text='Add New Contact' />

      <AddContact
        onSubmit={handleSubmit}
        onChangeName={handleNameChange}
        onChangeNum={handleNumberChange}
        valueName={newName}
        valueNum={newNumber}
        type='text'
      />
      <Header text='Contact List' />

      {persons.map((person) => (
        <p key={person.id}>
          {person.name} {person.number}
        </p>
      ))}
    </>
  );

AddContact component has a button element that can also be extracted into its own component called Button.js passing it a text props

import React from 'react';

const Button =({text})=>{
    return(
        <>
        <button>{text}</button>
        </>
    )
}

export default Button

Then we replace the button element with the Button component in the AddContact file

import Button from './Button';

   <form onSubmit={onSubmit}>
        <div>
          name: <input type={type} onChange={onChangeName} value={valueName} />
        </div>
        <div>
          number: <input type={type} onChange={onChangeNum} value={valueNum} />
        </div>
          <Button text='ADD NEW'/>
      </form>

Next, we create another component to render the contact list called Contacts.js, we pass in two props for the name and number(always using destructuring)

import React from 'react';

const Contacts = ({ name, number }) => {
  return (
    <>
      <p>
        {name} {number}
      </p>
    </>
  );
};
export default Contacts;

In App.js, we replace the p element with the Contacts component when mapping through persons array

import Contacts from './components/Contacts';

 {persons.map((person) => (
        <Contacts key={person.id} name={person.name} number={person.number} />
      ))}

We check our console to make sure there are no errors and test that we can still add contacts. The code looks cleaner and more readable now.

Creating a delete feature

We have to add a delete button to each contact on the list to be able to delete them individually. Since we already have the Button component, we easily import it into our Contacts component.

import Button from './Button';

const Contacts = ({ name, number }) => {
  return (
    <>
      <p>
        {name} {number}
        <Button text='Delete' />
      </p>
    </>
  );
};
export default Contacts;

In App.js, we create a function handleContactDelete to handle delete β€” For now, we would just log "delete' to the console

 const handleContactDelete = () => {
    console.log('delete');
  };

We pass this function as a prop to the Contacts component since that's where the Button component is located

  {persons.map((person) => (
        <Contacts
          key={person.id}
          name={person.name}
          number={person.number}
          handleDelete={handleContactDelete}
        />
      ))}

In the Contacts component file, we pass down handleDelete prop to the Button component

const Contacts = ({ name, number, handleDelete }) => {
  return (
    <>
      <p>
        {name} {number}
        <Button text='Delete' handleDelete={handleDelete} />
      </p>
    </>
  );
};

Finally, In the Button component file we add the onClick event to the button tag and assign it the handleDelete prop

const Button = ({ text, handleDelete }) => {
  return (
    <>
      <button onClick={handleDelete}>{text}</button>
    </>
  );
};

This process of passing down props through components that don't need them to get to components that need them is called prop drilling, it can be cumbersome when there are a lot of prop layers to get to β€” In a more complex project, React Context API is recommended to avoid prop drilling but for now, this will do.

Now, when we click on any delete button, we see 'delete' logged on our console. The handleContactDelete function and prop passing worked so we can go ahead to write a proper delete function.

Since we are trying to delete contacts individually, we go to the handleContactDelete function in our App.js file and pass it a parameter(id). We filter the persons array using the filter method and return persons that their ids do not match the id that's passed in as an argument. Then we update the persons state.

  const handleContactDelete = (id) => {
    console.log('delete');
    setPersons(
      persons.filter((l) => {
        return l.id !== id;
      })
    );
  };

We pass in an argument to the function, so we change the value passed in as prop in the Contacts componenthandleDelete={handleContactDelete} becomes handleDelete{()=>handleContactDelete(person.id)

Checking our application we can see the delete button works but it doesn't update the database when you refresh your page the deleted contact returns.

Deleting from the server

We use axios delete method to delete from the server, the method requires the URL of the individual contact that is to be deleted so we can use the id to locate that. When adding the URL to the axios delete method, we use template literals(backticks) so we can add the id params to the URL.

 const handleContactDelete = (id) => {
    axios
      .delete(`http://localhost:3001/persons/${id}`)
      .then((response) => {
      setPersons(
        persons.filter((l) => {
          return l.id !== id;
        })
      );
    });
  };

Now any deleted contact also gets deleted from the database.

Searching through contacts

I wrote an article about building a search component and we are going to follow the same steps there.

We create a new component called Search.js, the component returns an input field and two props passed from App.js

import React from 'react';

const Search = ({ type, value }) => {
  return (
    <>
      <div>
        Search Contacts <input type={type} value={value} />
      </div>
    </>
  );
};

export default Search;

In App.js, we import Search component and pass the props type and value to it.

import Search from './components/Search';

 <Search type='search' value='SEARCH CONTACT' />

We also need an onChange function to handle changes made in the value and we need a state to store the value from the input.

  const [search, setSearch] = useState('')

  const handleChange = (e) => {
    setSearch(e.target.value);
  };

<Search type='search' value={search} onChange={handleChange} />

We also have to update the Search component with the onChange prop that has been added

const Search = ({ type, value, onChange }) => {
  return (
    <>
      <div>
        Search Contacts <input type={type} value={value} onChange={onChange} />
      </div>
    </>
  );
};

The search bar is displayed on our page, but the functionality doesn't work yet. We have to filter through the persons state and return the contacts that match a letter or word typed in the search bar. We also use the toLowerCase() function to return correct data irrespective of caps.

  const filteredPersons = persons.filter((person) =>
    person.name.toLowerCase().includes(search.toLowerCase())
  );

We map through filteredPersons instead of mapping through persons state in App.js

{filteredPersons.map((person) => (
        <Contacts
          key={person.id}
          name={person.name}
          number={person.number}
          handleDelete={() => handleContactDelete(person.id)}
        />
      ))}

The search feature works properly now.

Recap

In this article, we created an add contact feature, added delete buttons, were able to delete individual contacts, and created a search feature that lets us search for a particular contact β€” We also learned how to update the backend for each of these functionalities.

It was interesting for me to code, test the app and write simultaneously πŸ˜ƒ.

In the next article, I would try to improve the app by adding a feature that allows already added numbers to be replaced by new ones and maybe give it a bit of styling.

As usual, pardon all grammatical errors and drop a comment if there are better or simpler ways to achieve these features. I would love to learn 😁

Link to repo

Β