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:
- 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.
- 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.
- 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; 