Search Filter for List

Develop a component that filters items in a list based on a text input search query.

Solution

Type your local borough

  • City of London
  • Barking and Dagenham
  • Barnet
  • Bexley
  • Brent
  • Bromley
  • Camden
  • Croydon
  • Ealing
  • Enfield
  • Greenwich
  • Hackney
  • Hammersmith and Fulham
  • Haringey
  • Harrow
  • Havering
  • Hillingdon
  • Hounslow
  • Islington
  • Kensington and Chelsea
  • Kingston upon Thames
  • Lambeth
  • Lewisham
  • Merton
  • Newham
  • Redbridge
  • Richmond upon Thames
  • Southwark
  • Sutton
  • Tower Hamlets
  • Waltham Forest
  • Wandsworth
  • Westminster

Explanation

The HTML Structure

The component is made up of an input and an unordered list where the list items are mapped over.

handleInput function

When the input is updated, the onChange event calls the handleInput function.

searchInput stores the current value of the input. Which is in turn filtered to return those values included in the list and stored in updatedList.

Debounce

Although not essential for this action, a debounce function is used to ensure that the list seen by the user isn't updated too quickly to become distracting.

The debounce function is often used to prevent too many API calls.

How Debounce works

The function takes a callback and a delay.

A timer is referenced at this level of scope. An anonymous function is returned that clears any current timer and sets a new one.

Now when the function is called before the delay time has passed, that callback is removed and a new delay is initiated.

Code

import { CSSProperties, useState } from "react";
import styles from "./Solution.module.css";

const londonBoroughs = [
  "City of London",
  "Barking and Dagenham",
  ...
  "Wandsworth",
  "Westminster",
];

const Solution = () => {
  const [filteredList, setFilteredList] = useState<string[]>(londonBoroughs);

  const debouncedSetFilteredList = debounceSetList((newList: string[]) => {
    setFilteredList(newList);
  });

  const handleInput: React.ChangeEventHandler<HTMLInputElement> = (event) => {
    const searchInput = event?.target.value;
    const updatedList = londonBoroughs.filter((boro) =>
      boro.toLowerCase().includes(searchInput.toLowerCase())
    );

    debouncedSetFilteredList(updatedList);
  };

  return (
    <div>
      <h2>Type your local borough</h2>
      <input onChange={handleInput} type="text" className={styles.input} />
      <ul className={styles.list}>
        {filteredList.map((boro) => (
          <li className={styles.li} key={boro}>
            {boro}
          </li>
        ))}
      </ul>
    </div>
  );
};

export default Solution;

// debounce function
function debounceSetList(
  // callback here is referencing setFilteredList
  // takes a string array and returns nothing
  callback: (newList: string[]) => void,
  delay: number = 500
): (newList: string[]) => void {
  let timer: number | null = null;

  return (newList: string[]) => {
    if (timer) {
      clearTimeout(timer);
    }
    timer = setTimeout(() => {
      callback(newList);
    }, delay) as unknown as number;
  };
}

Styling

.input {
  padding: 5px;
  margin: 30px 0;
}

.list {
  display: flex;
  flex-wrap: wrap;
  width: 500px;
  gap: 5px;
}

.li {
  padding: 3px;
  border: 1px solid grey;
  border-radius: 5px;
}

Links