React.js: How to set default value in Select option?

I am trying to implement select-option in React using custom hooks and encountered an issue while trying to set a default value in select option.
From the fetched data in UI, that comes from web API, I was able to show selected data based on category(in my case it’s cuisine). But when I select default value to show All data, state doesn’t update.
Another problem is about the duplicated values in select option. I need to have unique values as option values. I was thinking about to get unique values this way

 <option key={restaurant.id}>{[...new Set(restaurant.cuisine)]}</option>

But this removes duplicated characters,but not the duplicated values.

Code below.
Hooks/useRestaurants component

import React, { useState, useEffect } from "react";

const useRestaurants = (cuisine) => {
  const [allRestaurants, setAllRestaurants] = useState([]);

  useEffect(() => {
    fetch("https://redi-final-restaurants.herokuapp.com/restaurants")
      .then((res) => res.json())
      .then((result) => setAllRestaurants(result.results))
      .catch((e) => console.log("error"));
  }, []);

  useEffect(() => {
    if (cuisine === "All") {
      const filterRestaurants = [...allRestaurants].filter((restaurant) => // here is my try
       restaurant.cuisine.toLowerCase().includes(cuisine.toLowerCase())//code here doesn't work
      );
      setAllRestaurants(filterRestaurants);
    } else {
      const filterRestaurants = [...allRestaurants].filter((restaurant) =>
        restaurant.cuisine.toLowerCase().includes(cuisine.toLowerCase())
      );
      setAllRestaurants(filterRestaurants);
    }
  }, [cuisine]);

  return [allRestaurants];
};

export default useRestaurants;

App.js component

import React, { useState } from "react";

import useRestaurants from "./useRestaurants";
import Form from "./Form";
import Restaurant from "./Restaurant";
import "./styles.css";

export default function App() {
  const [cuisine, setCuisine] = useState("All");
  const [allRestaurants] = useRestaurants(cuisine);

  const onChangeHandler = (e) => {
    setCuisine(e.target.value);
  };

  return (
    <div className="App">
      <Form
        onChangeHandler={onChangeHandler}
        allRestaurants={allRestaurants}
        cuisine={cuisine}
        setCuisine={setCuisine}
      />
      {allRestaurants &&
        allRestaurants.map((restaurant) => (
          <Restaurant restaurant={restaurant} key={restaurant.id} />
        ))}
    </div>
  );
}

And Form.js component

import React from "react";

const Form = ({ allRestaurants, cuisine, onChangeHandler }) => {
  return (
    <select onChange={onChangeHandler} value={cuisine}>
      <option value={cuisine}>All</option>
      {allRestaurants.map((restaurant) => (
        <option key={restaurant.id}>{restaurant.cuisine}</option>
      ))}
    </select>
  );
};

export default Form;

Any help will be appreciated.

Answer

The useEffect in useRestaurants that is performing the filtering is missing allRestaurants from the dependency array. This means that the initial value (an empty array) will always be used within that useEffect. Thus, changing the cuisine will set allRestaurants to an empty array. However, you can’t add allRestaurants to the dependency array and set it from within the effect. That will cause it to loop infinitely. The solution is to not use an effect – just create the filtered result and return it either as a separate value or in place of allRestaurants

// useRestaurants.js
import { useState, useMemo, useEffect } from "react";

const useRestaurants = (cuisine) => {
  const [allRestaurants, setAllRestaurants] = useState([]);

  useEffect(() => {
    fetch("https://redi-final-restaurants.herokuapp.com/restaurants")
      .then((res) => res.json())
      .then((result) => setAllRestaurants(result.results))
      .catch((e) => console.log("error"));
  }, []);

  const filteredRestaurants = useMemo(() => {
    return cuisine === "All"
      ? allRestaurants
      : allRestaurants.filter((restaurant) =>
          restaurant.cuisine.toLowerCase().includes(cuisine.toLowerCase())
        );
  }, [cuisine, allRestaurants]);

  return [allRestaurants, filteredRestaurants];
};

export default useRestaurants;

To fix the duplicate cuisine values you need to create the Set and then filter over that result. Your form is still filtering over all allRestaurants and {[...new Set(restaurant.cuisine)]} is just creating an array with a single value.

// Form.js
import React from "react";

const Form = ({ allRestaurants, cuisine, onChangeHandler }) => {
  const cuisines = Array.from(new Set(allRestaurants.map((r) => r.cuisine)));

  return (
    <select onChange={onChangeHandler} value={cuisine}>
      <option value='All'}>All</option>
      {cuisines.map((cuisine) => (
        <option id={cuisine}>{cuisine}</option>
      ))}
    </select>
  );
};

export default Form;

Remember to loop over the filtered restaurants in App.js

...
const [allRestaurants, filteredRestaurants] = useRestaurants(cuisine);

...
return (
  ...
  {filteredRestaurants &&
    filteredRestaurants.map((restaurant) => (
      <Restaurant restaurant={restaurant} key={restaurant.id} />
  ))}
)

Leave a Reply

Your email address will not be published. Required fields are marked *