1

i'm trying to figure out an issue with looping using the WordPress REST API.

What I have:

I am using the rest api category endpoint "/wp/v2/categories". I am looping through each category returned (7 categories in total) and creating a button for each category. I have that working so far. And the code for that looks like this:

const Posts = ({ state }) => {

  const [categories, setCategories] = useState([]);
  useEffect(() => {
    fetch(state.source.api + "/wp/v2/categories")
      .then(response => response.json())
      .then(data => {
        setCategories(data);
      })
  }, []);
  console.log(categories);

  return (
    <>
      {categories.length > 0 ? (
        categories.map((category, i) => {
          return (
            <button key={i}>{category.name}</button>
          )
        })
      ) : (
          <p>Loading...</p>
        )
      }
    </>
  )
}

What I am trying to do: i am trying to have a section below the buttons that will list the blog posts for each category when the buttons are clicked. So when the page initially loads, all of the blogs posts are loaded, but when you click on the "Books" category, only the posts from the Books category would show, for example. I know that I have to create an event handler for the button, so I've updated the button to <button key={i} onClick={handleShowPosts}>{category.name}</button>, and began creating:

const handleShowPosts = () => {

};

But I am a little bit confused on exactly how to do this. Would I need to pass {category.name} into const handleShowPosts = ({category.name}) => {, and how would i do this while looping through each category to get the posts?

EDIT:

I should clarify that the endpoint to view all posts is /wp/v2/categories, but the endpoint to view posts of a specific category is "/wp/v2/posts?categories=" + category.id.

1
  • So, does the categories data have the posts? In your handler, you can set a new state and use category.name and then according to value, you can conditionally render the posts. Commented Sep 12, 2020 at 16:28

2 Answers 2

1

Well, If I'm not misunderstanding your goal and the data structure, here is an example showing how you can do it. As I tried to explain in my comment, you can set a category name state and conditionally render the posts.

const categoriesData = [
  {
    name: "books",
    posts: [
      { id: 1, name: "foo" },
      { id: 2, name: "bar" }
    ]
  },
  {
    name: "movies",
    posts: [
      { id: 1, name: "fizz" },
      { id: 2, name: "buzz" }
    ]
  }
];

function Main() {
  const [categories, setCategories] = React.useState(categoriesData);
  const [catName, setCatName] = React.useState();
  
  
  return (
    <React.Fragment>
      {categories.length > 0 ? (
        categories.map((category, i) => {
          return (
            <button key={i} onClick={() => setCatName(category.name)}>
              {category.name}
            </button>
          );
        })
      ) : (
        <p>Loading...</p>
      )}
      <div>
        {catName &&
          categories
            .find((category) => category.name === catName)
            .posts.map((post) => <div key={post.id}>{post.name}</div>)}
      </div>
    </React.Fragment>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<Main />, rootElement);
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>



<div id="root" />

Update after comments

If you are going to fetch the relevant posts data then you can use a useEffect for that. I'm mimicking the API request below. You can adapt your code according to that.

const categoriesData = [
  {
    name: "books"
  },
  {
    name: "movies"
  }
];

const postsData = {
  books: [
    { id: 1, name: "foo" },
    { id: 2, name: "bar" }
  ],
  movies: [
    { id: 1, name: "fizz" },
    { id: 2, name: "buzz" }
  ]
};

function fakePostsApi(catName) {
  return new Promise((resolve) =>
    setTimeout(() => {
      resolve(postsData[catName]);
    }, 1000)
  );
}

function Main() {
  const [categories, setCategories] = React.useState(categoriesData);
  const [catName, setCatName] = React.useState();
  const [posts, setPosts] = React.useState([]);

  React.useEffect(() => {
    if (catName) {
      fakePostsApi(catName)
        .then(setPosts);
    }
  }, [catName]);

  return (
    <div>
      {categories.length > 0 ? (
        categories.map((category, i) => {
          return (
            <button key={i} onClick={() => setCatName(category.name)}>
              {category.name}
            </button>
          );
        })
      ) : (
        <p>Loading...</p>
      )}
      <div>
        {posts.length === 0 ? (
          <p>No posts...</p>
        ) : (
          posts.map((post) => <div key={post.id}>{post.name}</div>)
        )}
      </div>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<Main />, rootElement);
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>



<div id="root" />

You can improve the code any way you like. For example, maybe a loading state and showing an "updating posts" message according to this. This is just an example which only aims to give you an idea.

Second Update

You are going to make the fetch request as you do for the categories. If you do the request with the category id, not with the name then you can change my example according to that. Instead of having a catName state you'll use catId state and keep a category id there. I'm providing just the relevant parts:

function Main() {
  const [categories, setCategories] = React.useState(categoriesData);
  const [catId, setCatId] = React.useState();
  const [posts, setPosts] = React.useState([]);

React.useEffect(() => {
  if (catId) {
    fetch(`${state.source.api}/wp/v2/posts?categories=${category.id}`)
      .then((response) => response.json())
      .then((data) => {
        setPosts(data);
      });
  }
}, [catId]);

and of course you should set category id within your button onCLick like that:

<button key={i} onClick={() => setCatId(category.id)}>
  {category.name}
</button>
Sign up to request clarification or add additional context in comments.

3 Comments

Thanks for the response. The endpoint to get the list of all the categories is /wp/v2/categories but the endpoint to get the posts for each specific category is /wp/v2/posts?categories= plus the category.id which i think could be set like <button key={i} categoryid={category.id} onClick={handleShowPosts}>{category.name}</button>. So on each button click, it would need to fetch data from a different endpoint with the category.id concatenated at the end
In this case, you can fetch posts data again by using category id state. You should use useEffect, fetch then update the posts data. I'm going to update my answer.
hmm, ok. the only part that is a little confusing to me is how i would add the "/wp/v2/posts?categories=" + category.id endpoint, since i am using an api instead of data from an array. I think the function fakePostsApi is what's throwing me off
0

If I understand correctly, you are looking for a means to structure your data from your end point and then create a UI where users can show/hide content based on a tag or category.

Below is an example of some data and UI that can function as a model for what you are looking for. You will likely need to structure your data in a similar format to take advantage of the ways the current functions store data and respond to user input. If you have control of your endpoint, you can restructure the data accordingly. Alternatively if you don't control the endpoint, you can create a function that structures it as needed. Lastly, you can always adapt this model to suit your data as needed.

CodeSandbox: https://codesandbox.io/s/stack-63862326-library-t1xx7

import React, { useState } from "react";
import styled from "styled-components";

// here is some example data that you should strive to construct if
// you happen to have control over your endpoint; otherwise you can
// reconstruct your data in a way that looks closer to this format

const exampleAPIDat = [
  {
    name: "Harry Potter",
    category: "fantasy",
    thumbnail:
      "https://images-na.ssl-images-amazon.com/images/I/81YOuOGFCJL.jpg"
  },

  {
    name: "Gardens of the Moon",
    category: "fantasy",
    thumbnail:
      "https://vignette.wikia.nocookie.net/malazan/images/6/67/GoTM_UK_AR.jpg/revision/latest?cb=20080327022138"
  },

  {
    name: "Use of Weapons",
    category: "Sci-Fi",
    thumbnail: "https://m.media-amazon.com/images/I/51NfClNMlhL.jpg"
  },

  {
    name: "Dune",
    category: "Sci-Fi",
    thumbnail: "https://m.media-amazon.com/images/I/41UZeCEKOBL.jpg"
  }
];

export default function App() {
  const [data, setData] = useState(exampleAPIDat);
  const [filter, setFilter] = useState([]);

  const filterFun = (event) => {
    // (1) update your filter array

    // category is passed from button
    let filtering = event.target.value;

    var newFilter;

    if (filter.includes(filtering)) {
      // remove if already in filter
      newFilter = [...filter].filter((f) => f !== filtering);
    } else {
      // add if not in the filter
      newFilter = [...filter, filtering];
    }

    setFilter(newFilter);

    // (2) update your data accordingly

    let newDat = exampleAPIDat.filter(
      (book) => !newFilter.includes(book.category)
    );

    setData(newDat);
  };

  const FilterButtons = ({ books }) => {
    // filter the data down to just categories
    let set = books.map((book) => book.category);
    let categories = [...new Set(set)];

    // produce a button for each category in the dat
    let btns = categories.map((category, index) => (
      <button key={category + index} value={category} onClick={filterFun}>
        {category}
      </button>
    ));

    return btns;
  };

  const AvailableBooks = ({ books }) =>
    books.map((book, index) => (
      <BookTile key={book.name + index} background={book.thumbnail}>
        <h3>{book.name}</h3>
        <h5>{book.category}</h5>
      </BookTile>
    ));

  return (
    <div>
      <ControlRow>
        <FilterButtons books={exampleAPIDat} />
      </ControlRow>
      <Library>
        <AvailableBooks books={data} />
      </Library>
    </div>
  );
}

const ControlRow = styled.div`
  display: flex;
  flex-direction: row;

  & button {
    margin: 16px;
  }
`;

const Library = styled.div`
  display: flex;
  flex-flow: row wrap;
`;

const BookTile = styled.div`
  display: flex;
  flex-direction: column;
  justify-content: flex-end;

  width: 200px;
  height: 400px;

  margin: 20px;

  background-image: linear-gradient(
      to top,
      rgba(255, 255, 255, 1),
      rgba(255, 255, 255, 0)
    ),
    url(${(props) => props.background});
  background-repeat: no-repeat;
  background-position: center;
  background-size: 200px 400px;

  -webkit-box-shadow: 10px 10px 5px 0px rgba(0, 0, 0, 0.75);
  -moz-box-shadow: 10px 10px 5px 0px rgba(0, 0, 0, 0.75);
  box-shadow: 10px 10px 5px 0px rgba(0, 0, 0, 0.75);

  &:hover {
    transform: scale(1.1);
  }

  & h3 {
    background-color: white;
    color: green;
  }

  & h5 {
    background-color: black;
    color: white;
  }
`;

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.