Overview
The pizza slice manages the pizza catalog, filtering, and sorting functionality. It handles both pre-loaded pizzas from JSON data and custom user-created pizzas, persisting filters and custom pizzas to localStorage.
State Shape
interface PizzaState {
pizzas: Pizza[];
filters: PizzaFilters;
loading: boolean;
error: string | null;
}
Array of all available pizzas, including both default catalog pizzas and custom user-created pizzas.
Current filter settings applied to the pizza catalog.Search query for filtering pizzas by name or ingredients.
Selected category filter (e.g., ‘all’, ‘Vegetarian’, ‘Meat’, ‘Seafood’, ‘Spicy’).
Maximum price filter. Defaults to 50 and is enforced to be at least 50 to prevent hiding new pizzas.
Current sort order. One of: ‘name-asc’, ‘name-desc’, ‘price-asc’, ‘price-desc’.
Error message if any operation fails, otherwise null.
Initial State
The initial state is hydrated from localStorage and JSON data:
{
pizzas: [...pizzasData, ...loadCustomPizzas()],
filters: {
search: '',
category: 'all',
maxPrice: 50,
sortBy: 'name-asc'
},
loading: false,
error: null
}
Persistence
- Custom pizzas: Stored in
localStorage under key custom_pizzas
- Filters: Stored in
localStorage under key pizza_filters
- Filters are automatically loaded on initialization and validated (maxPrice enforced to minimum of 50)
Actions
setSearch
Updates the search filter and persists to localStorage.
setSearch(search: string): void
Search query string to filter pizzas by name or ingredients.
Example Usage:
import { useDispatch } from 'react-redux';
import { setSearch } from './store/pizzaSlice';
const SearchBar = () => {
const dispatch = useDispatch();
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
dispatch(setSearch(e.target.value));
};
return <input onChange={handleSearch} placeholder="Search pizzas..." />;
};
Side Effects:
- Updates
state.filters.search
- Persists entire filters object to
localStorage.pizza_filters
setCategory
Updates the category filter and persists to localStorage.
setCategory(category: string): void
Category to filter by. Typically ‘all’, ‘Vegetarian’, ‘Meat’, ‘Seafood’, or ‘Spicy’.
Example Usage:
import { useDispatch } from 'react-redux';
import { setCategory } from './store/pizzaSlice';
const CategoryFilter = () => {
const dispatch = useDispatch();
return (
<select onChange={(e) => dispatch(setCategory(e.target.value))}>
<option value="all">All Categories</option>
<option value="Vegetarian">Vegetarian</option>
<option value="Meat">Meat</option>
<option value="Seafood">Seafood</option>
<option value="Spicy">Spicy</option>
</select>
);
};
Side Effects:
- Updates
state.filters.category
- Persists entire filters object to
localStorage.pizza_filters
setMaxPrice
Updates the maximum price filter and persists to localStorage.
setMaxPrice(maxPrice: number): void
Maximum price threshold for filtering pizzas.
Example Usage:
import { useDispatch } from 'react-redux';
import { setMaxPrice } from './store/pizzaSlice';
const PriceFilter = () => {
const dispatch = useDispatch();
const [price, setPrice] = useState(50);
const handlePriceChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = Number(e.target.value);
setPrice(value);
dispatch(setMaxPrice(value));
};
return (
<div>
<label>Max Price: ${price}</label>
<input
type="range"
min="10"
max="100"
value={price}
onChange={handlePriceChange}
/>
</div>
);
};
Side Effects:
- Updates
state.filters.maxPrice
- Persists entire filters object to
localStorage.pizza_filters
- Note: On load, maxPrice is enforced to be at least 50 to prevent new pizzas from being hidden
setSortBy
Updates the sort order and persists to localStorage.
setSortBy(sortBy: SortOption): void
Sort option. Must be one of: ‘name-asc’, ‘name-desc’, ‘price-asc’, ‘price-desc’.
Example Usage:
import { useDispatch } from 'react-redux';
import { setSortBy } from './store/pizzaSlice';
import type { SortOption } from './types';
const SortDropdown = () => {
const dispatch = useDispatch();
const handleSort = (e: React.ChangeEvent<HTMLSelectElement>) => {
dispatch(setSortBy(e.target.value as SortOption));
};
return (
<select onChange={handleSort}>
<option value="name-asc">Name (A-Z)</option>
<option value="name-desc">Name (Z-A)</option>
<option value="price-asc">Price (Low to High)</option>
<option value="price-desc">Price (High to Low)</option>
</select>
);
};
Side Effects:
- Updates
state.filters.sortBy
- Persists entire filters object to
localStorage.pizza_filters
addPizzaToCatalog
Adds a new custom pizza to the catalog and persists it to localStorage.
addPizzaToCatalog(pizza: Pizza): void
Complete pizza object to add to the catalog. Must include all required Pizza fields.
Example Usage:
import { useDispatch } from 'react-redux';
import { addPizzaToCatalog } from './store/pizzaSlice';
import type { Pizza } from './types';
const CreatePizzaForm = () => {
const dispatch = useDispatch();
const handleSubmit = (formData: any) => {
const newPizza: Pizza = {
id: `custom-${Date.now()}`,
name: formData.name,
price: formData.price,
ingredients: formData.ingredients,
category: formData.category,
imageUrl: formData.imageUrl,
isRecommended: false
};
dispatch(addPizzaToCatalog(newPizza));
};
return (
<form onSubmit={handleSubmit}>
{/* Form fields */}
</form>
);
};
Side Effects:
- Adds pizza to
state.pizzas array
- Loads existing custom pizzas from
localStorage.custom_pizzas
- Appends new pizza to the array
- Persists updated custom pizzas array to
localStorage.custom_pizzas
Reducer Logic
Filter Persistence
All filter actions (setSearch, setCategory, setMaxPrice, setSortBy) follow the same pattern:
- Update the specific filter property in state
- Serialize the entire
state.filters object to JSON
- Store in localStorage under key
pizza_filters
Custom Pizza Persistence
The addPizzaToCatalog action:
- Appends the new pizza to
state.pizzas
- Retrieves existing custom pizzas from localStorage
- Appends the new pizza to the custom pizzas array
- Stores the updated array in localStorage under key
custom_pizzas
Filter Loading
On initialization, filters are loaded with validation:
- Default filters are used if none exist in localStorage
maxPrice is enforced to be at least 50 using Math.max()
- This prevents new pizzas from being hidden by previously saved low price filters
Selectors
While not exported from the slice, common selector patterns:
import { RootState } from './store';
// Get all pizzas
const selectPizzas = (state: RootState) => state.pizza.pizzas;
// Get filters
const selectFilters = (state: RootState) => state.pizza.filters;
// Get filtered and sorted pizzas (typically in a component or custom selector)
const selectFilteredPizzas = (state: RootState) => {
const { pizzas, filters } = state.pizza;
return pizzas
.filter(pizza => {
const matchesSearch = pizza.name.toLowerCase().includes(filters.search.toLowerCase()) ||
pizza.ingredients.some(ing => ing.toLowerCase().includes(filters.search.toLowerCase()));
const matchesCategory = filters.category === 'all' || pizza.category === filters.category;
const matchesPrice = pizza.price <= filters.maxPrice;
return matchesSearch && matchesCategory && matchesPrice;
})
.sort((a, b) => {
switch (filters.sortBy) {
case 'name-asc': return a.name.localeCompare(b.name);
case 'name-desc': return b.name.localeCompare(a.name);
case 'price-asc': return a.price - b.price;
case 'price-desc': return b.price - a.price;
default: return 0;
}
});
};