0

I am creating a simple list of cards and I need to filter it according to which tags are selected. I have:

function Homepage() {
  const [cards, setCards] = useState([]);
  const [filteredCards, setFilteredCards] = useState([]);
  let filteredTags = [];

The tags are buttons with onClick={toggleSingleTag} and then I have this function:

function toggleSingleTag(e) {
  if (!filteredTags.includes(e.target.innerHTML)){
    filteredTags.push(e.target.innerHTML);
  } else {
    filteredTags = filteredTags.filter(function(elem){
      return elem != e.target.innerHTML;
    })
  }
  // setFilteredCards(filteredTags)
}

If I console.log filteredTags at the end of the function, I get the expected result (tags are included or removed from the list). But when I try to add setFilteredCards() (removing comment on the line) and console.log the values, I get that:

  1. filteredTags only ever includes the last clicked item (n), regardless of what was clicked at n-1. So it looks like the list is emptied everytime before the function is triggered, otherwise if n == n-1 the list should be empty.
  2. filteredCards includes the n-1 element and nothing else.

What am I doing wrong? How do I fix this?

Martina
  • 3
  • 1
  • Can you provide a demonstration of the problem? Any time you call `setFilteredCards` it will set `filteredCards` to whatever you pass it. In what way is that not working? – David Jul 13 '22 at 14:00
  • 2
    *"the list is emptied everytime before the function is triggered"* - More specifically, a new empty list is declared every time the component is rendered: `let filteredTags = [];` – David Jul 13 '22 at 14:02
  • How can I prevent that it empties? Should I declare it outside my Homepage component? – Martina Jul 13 '22 at 14:13
  • If you want to persist data between renders, that's what state is for. Just like you already do with your other state values. – David Jul 13 '22 at 14:16
  • I tried with the code from the other comment, which uses state, but it seems that functions inside of it don't work anymore. – Martina Jul 13 '22 at 14:25

2 Answers2

1

filteredTags is declared and defined as a new empty array with each component render:

let filteredTags = [];

If you want the value to persist across renders, that's what state is for:

const [filteredTags, setFilteredTags] = useState([]);

Just like with your other state values, this will define it as an empty array on the first render and allow you to update state with each render. For example, instead of this:

filteredTags.push(e.target.innerHTML);

You would update the state value:

setFilteredTags([...filteredTags, e.target.innerHTML]);

And instead of this:

filteredTags = filteredTags.filter(function(elem){
  return elem != e.target.innerHTML;
})

You would update the state value:

setFilteredTags(filteredTags.filter(function(elem){
  return elem != e.target.innerHTML;
}));

Basically, plain local variables are useful as helpers only for that render. Anything that should be maintained across renders belongs in state.

David
  • 208,112
  • 36
  • 198
  • 279
  • Thanks, this almost works! But I still get a problem: lets say I click on the tag "design". I get filteredList = []. Then I click another tag (eg. "news") and I get filteredList=['design']. Which is what I would have expected on the first click. And so on. It kinda works one step behind. Would you be able to explain me why? – Martina Jul 13 '22 at 14:48
  • @Martina: Where specifically are you observing this? The code shown produces no output, so it's not clear where you "get" these values. – David Jul 13 '22 at 14:49
  • Right, sorry; I added console.log(filteredTags) right before the closing braces of the function. – Martina Jul 13 '22 at 14:51
  • @Martina: Because state updated are asynchronous. You can find more information here: [The useState set method is not reflecting a change immediately](https://stackoverflow.com/q/54069253/328193) – David Jul 13 '22 at 14:52
0

This is because whenever setFilteredCards is triggered it will cause your state to update and component will rerender which is why your stored value in filteredTags is reinitiated once again.

function Homepage() {
  const [cards, setCards] = useState([]);
  const [filteredCards, setFilteredCards] = useState([]);
  const [filteredTags,setFilteredTags] = useState([]);

function toggleSingleTag(e) {
if (!filteredTags.includes(e.target.innerHTML)){
  setFilteredTags((ft)=>[...ft,e.target.innerHTML])
  //filteredTags.push(e.target.innerHTML);
} else {
  setFilteredTags((ft)=>{return ft.filter(function(elem){
         return elem != e.target.innerHTML;
     })
  })
  /*filteredTags = filteredTags.filter(function(elem){
    return elem != e.target.innerHTML;
  })*/
}
// setFilteredCards(filteredTags)

}

Kaneki21
  • 1,323
  • 2
  • 4
  • 22
  • I get an error with this code. Specifically, it says that includes is not a function. – Martina Jul 13 '22 at 14:20
  • `setFilteredTags((ft)=>ft.push(e.target.innerHTML))` - This will set `filteredTags` to a numeric value, not an array. – David Jul 13 '22 at 14:31
  • ok...should've used spread operator – Kaneki21 Jul 13 '22 at 14:33
  • I've updated the answer...you can also follow the other answer – Kaneki21 Jul 13 '22 at 14:35
  • The second use of `setFilteredTags` looks like it'll update the value to `undefined`. There's no implicit `return` in an arrow function when curly braces are used. – David Jul 13 '22 at 14:36
  • I tried with Array.from(ft) and the error goes away, but I get... numbers? On first click I get [''], on the second 2, then 1... At this point I am even more confused on what is going on. – Martina Jul 13 '22 at 14:36
  • There is implicit return in arrow functions when curly braces are not used – Kaneki21 Jul 13 '22 at 14:37
  • @Martina I've updated the code...you can have a try – Kaneki21 Jul 13 '22 at 14:38
  • @Kaneki21: *"There is implicit return in arrow functions when curly braces are not used"* - Correct. But that's not what you're doing here: `setFilteredTags((ft)=>{ft.filter(function(elem){` Note the curly brace at the start of the arrow function, just before `ft.filter`. So, as you say, there is an implicit return when curly braces **are not** used. But, as I said, there is **not** an implicit return when curly braces **are** used. – David Jul 13 '22 at 14:40