A React + Redux application for browsing characters from the Rick and Morty API. This project is designed as a learning resource, with extensive comments explaining React concepts, Redux patterns, and SOLID principles.
This project demonstrates:
- React Fundamentals: Components, Props, Hooks, JSX
- Redux State Management: Store, Slices, Actions, Thunks
- SOLID Principles: Applied throughout the codebase
- REST API Integration: Fetching and displaying data
- Component Architecture: Separation of concerns, reusability
- Advanced Patterns: Debouncing, search vs filters, state management
- UI Patterns: Collapsible components, conditional rendering, local vs global state
src/
βββ components/ # React components
β βββ ui/ # Reusable UI components (Loading, Error, Card, etc.)
β βββ CharacterList.jsx
β βββ CharacterSearch.jsx # Real-time search with debouncing
β βββ CharacterFilters.jsx
βββ store/ # Redux store configuration
β βββ store.js # Store setup
β βββ slices/ # Redux slices (feature-based state)
β βββ charactersSlice.js
βββ services/ # API service layer
β βββ api.js # All API calls
βββ types/ # Type definitions (JSDoc)
β βββ index.js
βββ App.jsx # Main app component
βββ main.jsx # Application entry point
βββ index.css # Global styles
Each module/component has one clear purpose:
services/api.js: Only handles API communicationstore/slices/charactersSlice.js: Only manages character statecomponents/ui/Loading.jsx: Only displays loading spinnercomponents/CharacterFilters.jsx: Only handles filtering UIcomponents/CharacterSearch.jsx: Only handles search functionality
- Redux slices can be extended with new actions without modifying existing ones
- Components accept props that allow customization without changing their code
- New filter types can be added without modifying existing filter logic
- UI components (Loading, Error, CharacterCard) are interchangeable
- They all follow consistent prop patterns
- Components only receive the props they need
- API functions are focused and don't require unnecessary parameters
- Components depend on abstractions (Redux store) not concrete implementations
- API service layer abstracts HTTP calls, making it easy to swap implementations
User Action
β
Component (e.g., CharacterFilters)
β
Dispatch Redux Action
β
Redux Slice (charactersSlice)
β
Async Thunk (fetchCharacters)
β
API Service (api.js)
β
Rick and Morty API
β
Response flows back up
β
Redux Store Updated
β
Components Re-render (via useSelector)
What are Components? Components are reusable pieces of UI. They're like JavaScript functions that return HTML (JSX).
Example:
function CharacterCard({ character, onClick }) {
return <div onClick={onClick}>{character.name}</div>;
}What are Props? Props (short for "properties") are how you pass data from parent to child components. They're read-only - a component cannot modify its own props.
Example:
// Parent component
<CharacterCard character={characterData} onClick={handleClick} />
// Child component receives props
function CharacterCard({ character, onClick }) {
// character and onClick are available here
}Key Rules:
- Props flow down (parent β child)
- Props are immutable (can't be changed by child)
- Props can be functions, objects, strings, numbers, etc.
Hooks are special functions that let you "hook into" React features.
Manages component-level state (data that can change).
const [count, setCount] = useState(0);
// count is the value, setCount is the function to update itPerforms side effects (API calls, subscriptions, DOM manipulation).
useEffect(() => {
// This runs after every render
fetchData();
}, [dependency]); // Only re-run if dependency changesReads data from Redux store.
const characters = useSelector((state) => state.characters.items);Gets the dispatch function to send actions to Redux.
const dispatch = useDispatch();
dispatch(fetchCharacters());JSX is JavaScript XML - it lets you write HTML-like syntax in JavaScript.
// JSX
const element = <h1>Hello, {name}!</h1>;
// Compiles to JavaScript
const element = React.createElement('h1', null, `Hello, ${name}!`);Rules:
- Must return a single root element (or use Fragment
<>...</>) - Use
classNameinstead ofclass - Use
{}for JavaScript expressions - Self-closing tags need
/:<img />
Redux is a state management library. Instead of passing data through many components (prop drilling), Redux provides a central "store" where any component can access or update state.
The store is the single source of truth for your application state.
// Creating a store
const store = configureStore({
reducer: {
characters: charactersReducer,
},
});A slice manages one piece of your application state. It contains:
- Initial state
- Reducers (synchronous state updates)
- Async thunks (for API calls)
Actions are plain objects that describe what happened. Redux Toolkit auto-generates them.
// Action creator (auto-generated)
dispatch(setFilters({ status: 'alive' }));
// Action object (what gets dispatched)
{ type: 'characters/setFilters', payload: { status: 'alive' } }Reducers are pure functions that specify how state updates in response to actions.
// Reducer function
setFilters: (state, action) => {
state.filters = { ...state.filters, ...action.payload };
}Thunks handle asynchronous operations (like API calls). They automatically dispatch pending/fulfilled/rejected actions.
export const fetchCharacters = createAsyncThunk(
'characters/fetchCharacters',
async (filters) => {
const response = await getCharacters(filters);
return response;
}
);- Node.js (v16 or higher)
- npm or yarn
- Install dependencies:
npm install- Start the development server:
npm run dev- Open your browser to
http://localhost:5173
npm run buildPurpose: Handles all HTTP requests to the Rick and Morty API.
Key Concepts:
- Separation of concerns: API logic is isolated
- Reusable functions: Can be used anywhere in the app
- Error handling: Centralized error management
Example:
export async function getCharacters(params = {}) {
const url = `${BASE_URL}/character?${queryParams}`;
return fetchData(url);
}Purpose: Configures the Redux store.
Key Concepts:
- Single source of truth
- Provider makes store available to all components
- Reducers manage different parts of state
Purpose: Manages character-related state.
Key Concepts:
- Initial state defines starting values
- Reducers handle synchronous updates
- Async thunks handle API calls
- Extra reducers handle thunk actions
Purpose: Allows users to filter characters.
Key Concepts:
- Controlled inputs (React controls the value)
- Local state for form inputs
- Syncs with Redux on submit
- Event handlers update state
Purpose: Displays list of characters.
Key Concepts:
- Connects to Redux (container component)
- useEffect fetches data when filters/page change
- Conditional rendering based on state
- Maps over array to render cards
Purpose: Reusable presentational components.
Key Concepts:
- Don't know about Redux
- Receive data via props
- Highly reusable
- Easy to test
-
Start with Components
- Read
src/components/ui/Loading.jsx- simplest component - Understand JSX syntax
- Learn about props
- Read
-
Learn State Management
- Read
src/components/CharacterFilters.jsx - Understand
useStatehook - See how state updates UI
- Read
-
Understand Redux
- Read
src/store/slices/charactersSlice.js - See how state is managed globally
- Understand actions and reducers
- Read
-
Study Data Flow
- Follow a user action from component β Redux β API β back
- See how
useEffecttriggers API calls - Understand how Redux updates trigger re-renders
-
Study Architecture
- Understand SOLID principles in practice
- See separation of concerns
- Learn component composition patterns
-
Redux Patterns
- Study async thunks
- Understand reducer patterns
- Learn about selectors
-
Performance
- Consider memoization opportunities
- Understand re-render triggers
- Study lazy loading
- React 18: UI library
- Redux Toolkit: State management
- Vite: Build tool and dev server
- Rick and Morty API: Data source
Every file in this project contains extensive comments explaining:
- What the code does
- Why it's structured that way
- How React/Redux concepts apply
- When to use certain patterns
src/services/api.js- API layer, SOLID principlessrc/store/slices/charactersSlice.js- Redux patternssrc/components/CharacterList.jsx- React hooks, Redux integrationsrc/components/CharacterFilters.jsx- Form handling, state managementsrc/components/CharacterSearch.jsx- Debouncing, search patterns, useRef hook
The application includes both Search and Filter functionality:
- Search: Real-time text search with intelligent triggering
- Triggers every 2 new characters typed, OR
- Triggers immediately when Enter is pressed
- Still uses debouncing for performance
- Filters: Structured filtering by status, species, type, and gender
- Combined: Search and filters work together for powerful filtering
See SEARCH_FEATURE_GUIDE.md for a detailed explanation of:
- How debouncing works
- Search vs filters differences
- useRef hook usage
- Combining search and filters
See SEARCH_TRIGGER_PATTERN.md for the new hybrid trigger pattern:
- Character count tracking
- Enter key handling
- Incremental search logic
- State management for triggers
The filter section includes a collapsible/expandable UI that demonstrates important state management concepts:
- Local UI State: Controls visibility (isExpanded)
- State Management Patterns: When to use local vs global state
- Conditional Rendering: Showing/hiding content based on state
- Auto-expand: Automatically expands when filters are active
See COLLAPSE_FEATURE_GUIDE.md for a detailed explanation of:
- Local state vs global state
- State lifecycle and re-renders
- Conditional rendering patterns
- Decision tree for state placement
-
Add a Character Detail View
- Create a new component to show full character details
- Use
fetchCharacterthunk from the slice - Add routing (consider React Router)
-
Add Locations Feature
- Create
locationsSlice.jsfollowing the same pattern - Create
LocationListcomponent - Add location filters
- Create
-
Improve Error Handling
- Add retry logic with exponential backoff
- Show more detailed error messages
- Add error boundaries
-
Add Search Debouncing
- Debounce the name filter input
- Prevent API calls on every keystroke
- Use a custom hook
-
Add Favorites
- Store favorite characters in localStorage
- Add favorite/unfavorite functionality
- Create a favorites view
This is a learning project. Feel free to:
- Add more features
- Improve documentation
- Refactor code
- Add tests
This project is for educational purposes.
Happy Learning! π