kb_
kb_

Reputation: 187

Filter items by category - React

I have a list of posts that are organized by category. Currently it lists them on the page like:

Leisure
    -leisure post 1
    -leisure post 2
    -leisure post 3
Sports
    -sports post 1
    -sports post 2
    -sports post 3
...and so on (these are not the exact categories and posts, just and example of how they are listed on the page right now)

which is created with the following component:

const Posts = ({ state }) => {
  const postsPerCategory = getPostsGroupedByCategory(state.source);

  return (
    <>
      {postsPerCategory.map(({ category }, index) => {
        return (
          <button key={index}>{category.name}</button>
        )
      })}

      {postsPerCategory.map(({ posts, category }, index) => {
        return (
          <div key={index}>
            <h4>{category.name}</h4>
            {posts.map((post, index) => {
              return (
                <article key={index}>
                  {post.title.rendered}
                </article>
              )
            })}
          </div>
        )
      })}
    </>
  )
}

The console.log(postsPerCategory) returns an array with objects in the following image:

enter image description here

I've created a div that maps the postsPerCategory so that there is a button for each category. But I am trying to use hooks so that when each category button is selected only the posts from that category are listed. I am new to using hooks, so I am really lost on how to create the onClick function that would do what I am needing. So far for the buttons I have the following, which is already in the code above:

{postsPerCategory.map(({ category }, index) => {
  return (
    <button key={index}>{category.name}</button>
  )
})}

How would I create the function that does what i am looking for?

Upvotes: 0

Views: 9038

Answers (3)

Patrick Sheen
Patrick Sheen

Reputation: 11

Based on the first answer, you can set the default category name as 'all', then check the value of categoryName==='all', if it's true, map all the postsPerCategory, otherwise check categoryName === category.name in the map function. This may not be the most efficient solution, but it works!

{
   categoryName==='all'? postsPerCategory.map(({ posts, category }, index) => 
     (
      <div key={index}>
        <h4>{category.name}</h4>
        {posts.map((post, index) => {
          return (
            <article key={index}>
              {post.title.rendered}
            </article>
          )
        })}
      </div>
     ) : postsPerCategory.map(({ posts, category }, index) => {
    return categoryName === category.name? (
      <div key={index}>
        <h4>{category.name}</h4>
        {posts.map((post, index) => {
          return (
            <article key={index}>
              {post.title.rendered}
            </article>
          )
        })}
      </div>
    ) : null
  })
 
}

Upvotes: 1

Cagri Uysal
Cagri Uysal

Reputation: 394

I would create a state for tracking whether each category is selected or not.

const [isSelected, setIsSelected] = useState({});

You should initialize this state when the app first rendered. This can be done with useEffect.

useEffect(() => {
  const isSelected = {};

  const categoryNames = postPerCategory.map({category} => category.name);
  categoryNames.forEach(categoryName => isSelected[categoryName] = false)

  setIsSelected(isSelected);
}, [])

Then when the button is clicked, the checked state should be toggled, as follows.

<button key={index} onClick={() => setIsSelected({...isSelected,
    [category.name] : !isSelected[category.name]})}>
  {category.name}
</button>

Finally, render the category if it is checked.

{
  postsPerCategory.filter({category} => isSelected[category.name])
    .map({category} => {
      // render here
    })
}

Upvotes: 1

Christopher
Christopher

Reputation: 168

Using hooks in this case would be just for the category.name itself, so you can setup a hook like this:

const [categoryName, setCategoryName] = useState(yourDefaultCategory);

Then inside your button all you have to do is add an onclick event that calls setCategoryName(category.name) like so:

<button key={index} onClick={() => setCategoryName(category.name)}>
  {category.name}
</button>

For this to have any effect on the list you need to filter your map list by category like so:

{postsPerCategory.map(({ posts, category }, index) => {
        return categoryName === category.name? (
          <div key={index}>
            <h4>{category.name}</h4>
            {posts.map((post, index) => {
              return (
                <article key={index}>
                  {post.title.rendered}
                </article>
              )
            })}
          </div>
        ) : null
      })}

I haven't tested the above but it should work fine. You could also consider using filter instead of map. Will be a bit cleaner.

Upvotes: 2

Related Questions