So I tried to modify the tagList using setTagList(). It changed the content of the page (it adds tags in the <Filter /> component) but I cannot implement that each tag in <Filter /> is unique.
import Filter from "./components/Filter";
import Card from "./components/Card";
import data from "./assets/data.json";
import { useState, useEffect } from "react"
export default function App() {
const [cards, setCards] = useState([]);
const [tagList, setTagList] = useState([]);
useEffect(() => {
generateCards();
}, []);
function addTag(newTag) {
// console.log(tagList) always outputs []
if (!tagList.includes(newTag)) {
setTagList(oldTagList => [...oldTagList, newTag])
// console.log(tagList) here always outputs []
// But if I change "const [tagList, setTagList] = useState([]);" to "let [tagList, setTagList] = useState([]);" and add "tagList = [...tagList, newTag]" here, it works
}
}
function deleteTag(tag) {
setTagList((oldTags) => oldTags.filter((oldTag) => oldTag !== tag));
}
function clearTags() {
setTagList([]);
}
function generateCards() {
setCards(
data.map((cardData) => (
<Card
key={cardData.id}
logo={cardData.logo}
company={cardData.company}
isNew={cardData.new}
isFeatured={cardData.featured}
position={cardData.position}
postedAt={cardData.postedAt}
contract={cardData.contract}
location={cardData.location}
tags={[
cardData.role,
cardData.level,
...cardData.languages,
...cardData.tools,
]}
addTag={addTag}
filter={filter}
/>
))
);
}
// console.log(tagList) works normally
return (
<div className="bg-bg-color h-screen">
<div className="relative bg-wave-pattern w-full h-28 text-bg-color bg-primary">
{tagList.length !== 0 && (
<Filter tagList={tagList} deleteTag={deleteTag} clearTags={clearTags} />
)}
</div>
<div className="2xl:px-96 xl:px-64 lg:px-32 md:px-16 sm:px-4 py-16 space-y-8">{cards}</div>
</div>
);
}
I tried to console.log(tagList) in the addTag() function but it always returns the default value. But when console.log(tagList) outside the function, it works like normal.
But when I changed the const keyword to let when setting the state and add "tagList = […tagList, newTag]" right after setTagList(…), it works.
Is React broken?
2
Answers
The
generateCards
function is only called from theuseEffect
above, which has no dependencies, meaning it’s only called when the component is mounted.This means that the
addTag
callback prop from theCard
component always references the sameaddTag
function that was created on component mount, so its closure only sees the initialtagList
value ([]
).One quick fix might be to add
tagList
as dependency to theuseEffect
that callsgenerateCards
, so that all cards are re-rendered every timetagList
changes, making the newly rendered cards reference the most recentaddTag
function that also has the most recenttagList
value in its context:However, I would advise you to use
useMemo
rather than storing JSX in the state, as well as updating youraddTag
function to check if a tag is included in the currently selected ones that come from the previous state, rather than using thetagList
from the function’s context:The issue here is that you only update the value of
cards
(usingsetCards
) one time—right after the component mounts. This is because you only callgenerateCards
within auseEffect
hook with an empty dependency array so it only runs the first time the component renders. Since the value ofcards
is the one from the first render, theaddTag
function given to each card is the initial value ofaddTag
, which references the initial value oftagList
(an empty array).While you can fix this by adding
tagList
to the dependency array of theuseEffect
hook, a more straightforward way to handle this would be to put the conditional logic ofaddTag
within the callback passed tosetTagList
, like this:Additionally, there is no need to store
cards
in state, and doing so can introduce problems such as the one you have encountered here. If you need to optimize performance, you should useuseMemo
, but I would start out just rendering the cards directly in thereturn
statement, like this: