Welcome to the first hands-on unit of this course. We are moving from static screens to a playable experience by wiring user actions to state changes. You will build a dynamic actions panel and ensure that every update to game state is immutable and predictable. This foundation will allow us to add time-based logic and win/loss conditions in later units.
We will render action buttons from a configuration array and dispatch reducer actions when the player clicks them. Each action decides if it is currently enabled based on the game state. This keeps components simple and declarative.
src/components/GameActions.jsx
What this does:
useGameContextgives youstateanddispatchfrom a shared context.actionsis a small config that drives the UI — name, icon, enabled condition, and the dispatch to fire.
The reducer is the single source of truth for game logic. It validates preconditions (guards), applies costs, and updates multiple slices of state immutably. Below are the two cases we dispatch from the actions above.
src/context/gameReducer.js
Key takeaways:
- Guards prevent invalid moves (e.g., not enough beans/energy or an empty queue).
- Costs and rewards are applied in one atomic update.
- Immutable updates use the spread operator to copy
state,resources, andshopbefore changing fields. This is crucial for predictable renders and time-travel/debugging patterns.
In this unit, you built a config-driven action panel that dispatches events and a reducer that applies guarded, immutable updates across multiple state slices. Your component stayed clean and focused on rendering, while the reducer centralized game rules.
You are ready to practice by wiring more actions and strengthening your reducer logic. Let’s head to the practice section and make the café come alive.
