To create a dynamic, searchable list of categories and posts using React within WordPress, create a custom page template file (e.g., custom-page.php) in your child theme directory (e.g., astra-child).
This approach uses PHP to fetch all categories and their associated posts from the WordPress database, encodes that data into JSON, and passes it to a React frontend for rendering. Once the file is created, you can assign this template to any page by selecting 'React Categories List' from the Template dropdown in the WordPress page editor sidebar.
<?php
/*
Template Name: React Categories List
*/
get_header();
// Fetch all categories
$categories = get_categories(array(
'hide_empty' => false,
));
// Fetch posts for each category
$categories_with_posts = array();
foreach ($categories as $category) {
$posts = get_posts(array(
'category' => $category->term_id,
'numberposts' => -1,
));
$categories_with_posts[] = array(
'category' => $category,
'posts' => $posts,
);
}
// Convert data to JSON for JavaScript use
$categories_with_posts_json = json_encode($categories_with_posts);
?>
<div id="app"></div>
<?php
get_footer();
?>
<!-- Load React, ReactDOM, and Babel -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.26.0/babel.min.js"></script>
<script type="text/javascript">
const categoriesWithPosts = <?php echo $categories_with_posts_json; ?>;
</script>
<script type="text/babel">
const { useState, useEffect } = React;
const CategoryList = ({ categories }) => {
const [searchTerm, setSearchTerm] = useState('');
const filteredCategories = categories.filter(categoryData =>
categoryData.category.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
categoryData.posts.some(post => post.post_title.toLowerCase().includes(searchTerm.toLowerCase()))
);
return (
<div className="categories">
<input
type="text"
placeholder="Search categories or posts..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="search-input"
/>
{filteredCategories.map((categoryData, index) => (
<Category key={index} categoryData={categoryData} />
))}
{filteredCategories.length === 0 && <p>No categories or posts found.</p>}
</div>
);
};
const Category = ({ categoryData }) => {
const { category, posts } = categoryData;
const [isExpanded, setIsExpanded] = useState(true);
return (
<div className="category">
<h2 onClick={() => setIsExpanded(!isExpanded)}>{category.name}</h2>
{isExpanded && (
posts.length > 0 ? (
<ul>
{posts.map(post => (
<li key={post.ID}>
<a href={post.guid} dangerouslySetInnerHTML={{ __html: post.post_title }}></a>
</li>
))}
</ul>
) : (
<p>No posts available</p>
)
)}
</div>
);
};
const App = () => {
const [categories, setCategories] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
setCategories(categoriesWithPosts);
setLoading(false);
}, []);
if (loading) return <p>Loading...</p>;
return (
<div>
<h1>All Categories and Posts</h1>
<CategoryList categories={categories} />
</div>
);
};
ReactDOM.render(<App />, document.getElementById('app'));
</script>