I have a component Body.jsx
, it contained logic of fetching data from an API and filtering as well.
I tried to create a custom hook useResData
which separates out logic of fetching data as shown-
export const Body = () => {
const resDataList = useResData();
const [filteredList, setFiltererdList] = useState(resDataList);
const [inputText, setInputText] = useState('');
const filterList = () => {
let filteredList = resDataList.filter((item) => item.info.avgRating > 4);
setFiltererdList(filteredList);
}
if (resDataList.length === 0) return <Shimmer />;
return (
<div className="Body">
<div className="search">
<input
className="search-input"
value={inputText}
onChange={(e) => {
setInputText(e.target.value)
}}
/>
<button
className="search-btn"
onClick={() => {
let list = resDataList.filter((data) => data.info.name.toLowerCase().includes(inputText))
setFiltererdList(list);
}}
>
Search
</button>
</div>
<div className="filter-btn">
<button onClick={filterList}>Top Rated Restaurants</button>
</div>
<div className="cardContainer">
{filteredList.map((item) =>
<Link
to={'restaurants/' + item.info.id}
key={item.info.id}
>
<Card resData={item?.info} key={item.info.id} />
</Link>
)}
</div>
</div>
)
}
And custom hook is –
const useResData = () => {
const [resDataList, setResDataList] = useState([]);
useEffect(() => {
getData = async () => {
let res = await fetch(
"https://www.swiggy.com/dapi/restaurants/list/v5?lat=28.65200&lng=77.16630&is-seo-homepage-enabled=true&page_type=DESKTOP_WEB_LISTING"
);
let data = await res.json();
setResDataList(data?.data?.cards?.[4]?.card?.card?.gridElements?.infoWithStyle?.restaurants)
}
getData();
}, [])
return resDataList;
}
export default useResData;
Now the issue is when resDataList
is populated in Body.tsx
, it does not populate filteredList
. I know that for filteredList
to be populated I need to use setFiltererdList
, but I read somewhere that each time a component renders it creates a separate copy of state variable, by this logic const [filteredList, setFiltererdList] = useState(resDataList);
should initialize filteredList
with resDataList
each time Body
component is rendered.
2
Answers
The initial value of
useState
is only used on the first render, at which pointresDataList
is still empty.You could instead store a filter in the state and avoid redundant state by directly computing the filtered result during rendering.
This is because the
useEffect
hook runs at the end of the render cycle, so theBody
component was passed the initial, unpopulatedresDataList
state value which is the empty array.This is not true. React components mount and the state is initialized exactly once. After that it’s up to the component to update and maintain its own state as necessary.
The trivial solution is to use a
useEffect
hook with a dependency on the returnedresDataList
value and enqueue state updates to synchronize the localfilteredList
state.Example:
Be aware that as-is this is a general React anti-pattern and should be avoided, e.g. copying the derived filtered value from the "useResData" state into
Body
. Just about any time you catch yourself having coded auseState
–useEffect
couplet you should actually use theuseMemo
hook to compute and provide a memoized/stable derived/computed value to the component.Instead of waiting for the user to click a button to filter the results, just compute and return the correct data to be rendered. Use the
inputText
to also filter further inline when rendering, or if you feel this is computationally expensive, you can just include this in the memoization.or