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:
- Fetch all the categories (which you might have already done in your app).
- Build a reverse index, with the shape
[parentId: number] => number[]
- 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