How to get all posts from parent and children categories?

I had the same use case than you — calling WP-API from React — and ran into the same issue.

Unfortunately WP-API does not support filtering by parent categories. In order to support it, you could extend the API’s functionality, adding a new filter that will run a tax_query behind the scenes, similar to Faye’s answer.

Assuming that you have access to the WordPress application, modify the functions.php in the current theme adding:

// Add parent_category filter to REST API
function rest_filter_by_parent_category($args, $request) 
{
  if (isset($request['parent_category'])) {
    $parent_category = sanitize_text_field($request['parent_category']);
    $args['tax_query'] = [
      [
        'taxonomy'         => 'category', 
        'field'            => 'term_id', 
        'include_children' => true,
        'operator'         => 'IN',
        'terms'            => $parent_category, 
      ]
    ];
  }
  return $args;
}

add_filter('rest_post_query', 'rest_filter_by_parent_category', 10, 3);

Then, just query the API with the new filter: /posts?parent_category=28

Alternative

If you cannot modify the functions.php file (e.g. you are querying an external blog), then you could:

  1. Fetch all the categories (which you might have already done in your app).
  2. Build a reverse index, with the shape [parentId: number] => number[]
  3. Build the query using categories=C1,C2... using the reverse index.
const axios = require("axios");

// Query all the categories from WordPress
const fetchCategories = async () => {
  const params = [
    "_fields[]=id",
    "_fields[]=parent",
    // Ideally you would use a better pagination strategy
    "page=1",
    "per_page=100",
  ];

  // Using axios, but you could use native fetch or any other library
  return axios
    .get(`WORDPRESS_SITE/wp-json/wp/v2/categories?${params.join("&")}`)
    .then((result) => result.data);
};

// Build reverse index
const buildParentIndex = (categories) => {
  return categories.reduce((acc, category) => {
    const hasParent = !!category.parent; // Root categories have ID 0

    const parentId = hasParent ? category.parent : category.id;
    if (!acc[parentId]) {
      acc[parentId] = [];
    }

    if (hasParent) {
      acc[parentId].push(category.id);
    }

    return acc;
  }, {});
};

(async () => {
  // You should pre-compute & cache these, as fetching the categories
  // and building the index on every request will heavily affect
  // the latency of your request
  const categories = await fetchCategories();
  const parentIndex = buildParentIndex(categories);

  const fetchPostsByParentId = (categoryId) =>
    axios
      .get(
        `WORDPRESS_SITE/wp-json/wp/v2/posts?categories${parentIndex[
          categoryId
        ].join("&")}`
      )
      .then((result) => result.data);
})();

If possible, I’d suggest to go with the first approach — modifying functions.php — as it’s simpler and more consistent. The JS alternative will likely require caching for that the latency not to be impacted, which brings a lot of potential issues (e.g. stale cache).

Source

Leave a Comment