How to filter a list using useEffect, useState, Typescript

Viewed 697

I have a simple React App that shows a table with names and details when the page is loaded. To fetch the data from the API I am using useEffect(). All works fine.

Now I want to implement a filter, where the user put the name (or part of it) and the table is updated/filtered.

I have tried some code, but the only thing I got was to fetch the data, filter it and then show only the filtered content. But what I want is to first all names on page load and just filter if the user clicks on search button.

How would you implement it? Thanks in advance!

My code so far:

const Search = () => {

   type FormData = {
      name: string
   }

   const [nameResponse, setNameResponse] = useState<NameResponse>()
   const [activePage, setActivePage] = useState(0)
   const { register, handleSubmit } = useForm<FormData>()
   const [searchName, setSearchName] = useState('')

   useEffect(() => {
      const params = {
         page: activePage,
         linesPerPage: 5
      }

      makeRequest({ url: `/names`, params })
         .then(response => setNameResponse(response.data))

   }, [activePage])

   const onSubmit = (data: FormData) => {
      setSearchName(data.name)

   }

   return (
      <div className="search-container">
         <div className="search-options">
            <form onSubmit={handleSubmit(onSubmit)}>
               <fieldset>
                  <legend>Search name:</legend>
                  <label htmlFor="name-input">Name:</label><br />
                  <input
                     type="text"
                     className="name-input"
                     name="name"
                     required
                     ref={register}
                  />
                  <ButtonIcon text="Search" />
               </fieldset>
              
            </form>
         </div>
         <div className="search-result">
            <h3 className="search-result-title">Search results</h3>
            <table className="table table-striped table-hover table-sm" cellPadding="0" cellSpacing="0">
               <thead className="thead-dark">
                  <tr>
                     <th>NAME</th>
                     <th>MEANING</th>
                     <th>GENDER</th>
                     <th>ORIGIN</th>
                  </tr>
               </thead>
               <tbody>
                  {nameResponse?.content.map(nameData => (
                     <tr key={nameData.id}>
                        <td>{nameData.name}</td>
                        <td>{nameData.meaning}</td>
                        <td>{nameData.gender}</td>
                        <td>{nameData.origin.name}</td>
                     </tr>
                  ))}
               </tbody>
            </table>
            {nameResponse &&
               <Pagination
                  totalPages={nameResponse.totalPages}
                  activePage={activePage}
                  onChange={page => setActivePage(page)}
               />
            }
         </div>

      </div>
   )

}

export default Search
2 Answers

You have to apply filtering only if searchName isn't empty. Also you should to implement mechanism where the user would be able to reset searchName. For example, you can listening on change the input and reset when the input value will be empty (clear).

Create a variable which will equal the Object[], the data you fetched, fi filteredResponse.

If the input is not empty apply filtering

 let filteredResponse = nameResponse?.content;
  if (searchName) {
    filteredResponse = nameResponse?.content.filter((el: any) =>
      el.name.toLowerCase().includes(searchName)
    );
  }

Optionally convert to lower case the names in case capital letters are used

use value and onChange event listener on input

<input
   type="text"
   className="name-input"
   name="name"
   required
   value={searchName}
   onChange={(e: any) => setSearchName(e.target.value)}
              // ref={register}
  />

and then use the filterResponse value to map over the fetched items:

{filteredResponse?.map((nameData: any) => (
          <tr key={nameData.id}>
            <td>{nameData.name}</td>
            <td>{nameData.meaning}</td>
            <td>{nameData.gender}</td>
            <td>{nameData.origin.name}</td>
          </tr>
        ))}

I commented some parts and also used dummy data to make it reproducible: Demo

Related