DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

Cover image for Optimized React Search Bar πŸ”―
Dheeraj Purohit
Dheeraj Purohit

Posted on

Optimized React Search Bar πŸ”―

Recently while learning react I came across a term called debouncing ??? so what does it mean ?
let's decode it together and understand how we can use debouncing in optimizing our search/ filter component

Let's Learn , Build and Share

Learn

DebouncingπŸ€

  • Debouncing refers to an optimization technique used to regulate the timeframe/interval after which a task will be executed.

  • for example, in search bar every state change (onChange) will cause the component re-render multiple times based on input given.

  • not really good if our component, re-renders such multiple time

Problem Statement for search component

  • on every event(onChange) state gets updated and component re-renders...
  • we can optimize it by providing a slight delay in state update using debouncing.

Build

talk is cheap, show me the code

Absolutely! let's deep dive into code right away, we are going to build an search bar which will filter data based on given input and display the corresponding data for it (search pokemon name and display the character accordingly)

  • for build part i am going to use codesandbox
  • Use React + typescript template
  • follow along 😊

Steps to be followed

  • html for view to be rendered
  • setup event handlers, states(can you guess what will be our states ?)
  • making an api call after render (effects)
  • add debouncing (hook) to limit the state update
export default function App() {
  return (
    <div className="App">
      <input type="text" />
      <ul>
        {/* here we will map our data and display */}
        <li>Pokemon Name</li>
        <li>
          <img src="#" alt="pokemon" />
        </li>
      </ul>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Output so far:

Output of basic html view

states, event handlers, types (its good to start including ts in code, event small bits can be helpful to learn typescript)

import { useState } from "react";
import "./styles.css";

type Pokemon = {
  name: string;
  image: string;
};

export default function App() {
  const [filter, setFilter] = useState<string>("");
  const [pokemonData, setPokemonData] = useState<Pokemon[]>([]);

  return (
    <div className="App">
      <input
        value={filter}
        type="text"
        onChange={(e) => setFilter(e.target.value)}
      />
      <ul>
        {pokemonData.map((pokemon) => (
          <li key={pokemon.name}>
            <h1>{pokemon.name}</h1>
            <img src={pokemon.image} alt={pokemon.name} />
          </li>
        ))}
      </ul>
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode
  • the code is pretty straight forward
  • we are using filter state variable for storing input value (e.target.value) remember ?
  • pokemonData state variable will be used for storing an array of objects coming from api
  • some types are included (use TS documentation if it seems confusing)

Api Calls with Side Effects (useEffect)πŸ”₯

  useEffect(() => {
    // will be called after every state (filter) update
    console.log("called");

    const getPokemonData = async () => {
      const pokemonList = await fetch(
        `https://pokeapi.co/api/v2/pokemon/${filter}`
      );

      const pokemonListToJson = await pokemonList.json();

      return {
        ...pokemonListToJson,
        name: pokemonListToJson.name,
        image: pokemonListToJson.sprites?.front_shiny
      };
    };

    getPokemonData().then((pokemon) => setPokemonData([pokemon]));
  }, [filter]);
Enter fullscreen mode Exit fullscreen mode
  • here comes the OG part where we are calling an api
  • useEffect takes two params - (callback function, dependency array)
  • so our dependency array is [filter], which means on every updates done by setFilter , filter state variable will be updated (user input) and cause the callback function to be called inside effect
  • in callback function we are making an async call with async-await syntax (pretty simple to use, have a quick glance on syntax if you are not familiar with it)
  • once we get data we store that object in pokemonData array with the setter function setPokemonData().

Output of view after adding useEffect hook

Chimchar
Chimchar

Gengar

Gengar

everything works fine till now,

so where's the problem

let me take you to the problem (re-rendering)

re-rendering

  • its a log for query="gengar" where our side-effects were called six time causing component re-render on each change made to filter state variable

  • this can drastically affects the performance of our app on large-scale


Solution

Debounce

  • to debounce our input we will use useDebounce hook
  • Installation :- npm i use-debounce
  • if using codesandbox, add dependency from bottom left panel by typing use-debounce

add debouncing (hook) to limit the state update

import { useEffect, useState } from "react";
import "./styles.css";
import { useDebounce } from "use-debounce";

type Pokemon = {
  name: string;
  image: string;
};

export default function App() {
  const [filter, setFilter] = useState<string>("");
  const [debouncedFilter] = useDebounce(filter, 500);
  const [pokemonData, setPokemonData] = useState<Pokemon[]>([]);

  useEffect(() => {
    // will be called after every state (filter) update
    console.log("side effect called");

    const getPokemonData = async () => {
      const pokemonList = await fetch(
        `https://pokeapi.co/api/v2/pokemon/${filter}`
      );

      const pokemonListToJson = await pokemonList.json();

      return {
        ...pokemonListToJson,
        name: pokemonListToJson.name,
        image: pokemonListToJson.sprites?.front_shiny
      };
    };

    getPokemonData().then((pokemon) => setPokemonData([pokemon]));
  }, [debouncedFilter]);

  return (
    <div className="App">
      <input
        value={filter}
        type="text"
        onChange={(e) => setFilter(e.target.value)}
      />
      <ul>
        {pokemonData.map((pokemon) => (
          <li key={pokemon.name}>
            <h1>{pokemon.name}</h1>
            <img src={pokemon.image} alt={pokemon.name} />
          </li>
        ))}
      </ul>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode
  • import useDebounce in your file
  • useDebounce accepts a value and delay
  • we have passed filter(search value) to useDebounce(filter,500) with a delay of 500ms
  • debouncedFilter state variable will store the filter value passed into debounce hook.
  • take your time to understand the flow , how we are passing the data into debounce hook
  • and finally time to change our dependency arr, earlier our side effect was called everytime filter state variable is updated
  • now side-effect will be called when debouncedFilter will update on a interval of 500ms.
  • so we achieved some sort of optimization in our search bar
  • state will be updated on every 500ms and not one every input change done by user.

Final Code Sandbox Solution

Thank you for making this far!
Today we covered a lot be it states, effects, de-bouncing, and of-course got to know about pokemon

So congratulations!πŸŽ‰
keep learning keep building 🀘


You can connect with me :

Twitter LinkedIn Github
Twitter LinkedIn github

Top comments (2)

Collapse
 
mhcrocky profile image
mhcrocky

so great article!!!!!❀❀❀❀❀

Collapse
 
purohitdheeraj profile image
Dheeraj Purohit

Glad to hear it from you
thank you 🀩🀘

Update Your DEV Experience Level:

Settings

Go to your customization settings to nudge your home feed to show content more relevant to your developer experience level. πŸ›