Reputation: 1946
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.
Upvotes: 0
Views: 290
Reputation: 187
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} />
))}
)
Upvotes: 1