The Problem: Managing Complex Book Data Without Prop Drilling

When I started building AYUBOOKS, a digital cataloging and reading platform inspired by Letterboxd, the initial prototype relied heavily on local component state (useState). Everything worked smoothly with just a handful of books.

However, as the catalog grew and I introduced real-world features—such as instant search filtering, category tagging, tracking reading statuses, and user reviews—three major bottlenecks quickly emerged:

  1. Severe Prop Drilling: Passing book lists, search queries, and state-updating functions down through multiple layers of nested components (from the main layout to search bars, filter sidebars, and individual book cards) made the codebase rigid and hard to refactor.
  2. State Synchronization Bugs: Updating a book's status in one view (like marking it as "Read" inside a modal) didn't always reflect immediately in the main catalog grid or user statistics without messy workarounds.
  3. Unnecessary Re-renders: Every keystroke in the search bar triggered state updates at the top level, causing large portions of the UI tree to re-render and making the filtering experience feel sluggish.

To keep AYUBOOKS fast, scalable, and maintainable, I needed a centralized, predictable state management solution—which led me to refactor the core architecture using Redux Toolkit.

The Architecture: Centralizing State with Redux Toolkit

Instead of reaching for boilerplate-heavy classic Redux, I integrated Redux Toolkit (RTK) alongside React 19 and Vite. RTK gave me a single source of truth for the entire book catalog while keeping the logic modular and easy to debug.

Here is how I structured the Redux store folder inside the project:

 text src/ ├── app/ │ └── store.js # Configures the central Redux store └── features/ └── books/ ├── booksSlice.js # Manages catalog data, search query, and status updates └── BookList.jsx # Renders the filtered book grid cleanly`// src/features/books/booksSlice.js import { createSlice } from '@reduxjs/toolkit'; const initialState = { items: [], // Populated with book objects { id, title, author, category, status } searchQuery: '', selectedCategory: 'All', }; const booksSlice = createSlice({ name: 'books', initialState, reducers: { setBooks: (state, action) => { state.items = action.payload; }, setSearchQuery: (state, action) => { state.searchQuery = action.payload; }, setCategoryFilter: (state, action) => { state.selectedCategory = action.payload; }, toggleReadStatus: (state, action) => { const book = state.items.find((b) => b.id === action.payload); if (book) { book.status = book.status === 'Read' ? 'Want to Read' : 'Read'; } }, }, }); export const { setBooks, setSearchQuery, setCategoryFilter, toggleReadStatus } = booksSlice.actions; export default booksSlice.reducer;` // src/features/books/booksSlice.js import { createSlice } from '@reduxjs/toolkit'; const initialState = { items: [], // Populated with book objects { id, title, author, category, status } searchQuery: '', selectedCategory: 'All', }; const booksSlice = createSlice({ name: 'books', initialState, reducers: { setBooks: (state, action) => { state.items = action.payload; }, setSearchQuery: (state, action) => { state.searchQuery = action.payload; }, setCategoryFilter: (state, action) => { state.selectedCategory = action.payload; }, toggleReadStatus: (state, action) => { const book = state.items.find((b) => b.id === action.payload); if (book) { book.status = book.status === 'Read' ? 'Want to Read' : 'Read'; } }, }, }); export const { setBooks, setSearchQuery, setCategoryFilter, toggleReadStatus } = booksSlice.actions; export default booksSlice.reducer;