Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"cSpell.words": [
"Dodds",
"Karlovskiy"
]
}
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,21 @@ The **Code Snapshots** and **Lecture Attachments** folders contain one subfolder
Code snapshots are primarily provided to allow you to compare your code to mine. The snapshots are taken directly from the course recordings and therefore reflect my code you see in the videos.

Of course, you can also try running those code snapshots on your machine. You'll need to run `npm install` in the individual snapshot folders, followed by `npm run dev` to start the development server - just as shown in the course.

# Anton's Experiments

This repository includes experimental implementations by [anton-karlovskiy](https://github.com/anton-karlovskiy) that explore best practices and improvements to the course code.

**Branch:** `anton-experiment`

## 10 Advanced State Management with Context useReducer -> 09-dispatching-actions-finished

This experiment focuses on React Context best practices and TypeScript migration:

- Porting the shopping-cart-context from JavaScript to TypeScript
- Implementing React Context following best practices (as outlined in [Kent C. Dodds' guide](https://kentcdodds.com/blog/how-to-use-react-context-effectively))
- Refactoring the context implementation with improved naming and structure

**Related Links:**
- [GitHub Pull Request #84](https://github.com/academind/react-complete-guide-course-resources/pull/84)
- [Live Demo (Vercel)](https://react-complete-guide-course-resources-10-9.vercel.app/)
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# Elegant Context

A shopping cart application built with React, designed to practice **best practices of `useReducer` and React Context**.

## Overview

Elegant Context is a small e-commerce-style app where users can browse clothing products, add them to a cart, and adjust quantities. The focus is on clean, scalable state management using the Context API combined with `useReducer`.

## Tech Stack

- **React 19** with TypeScript
- **Vite** for build tooling
- No external state management libraries—pure React patterns

## Getting Started

```bash
# Install dependencies
pnpm install

# Start development server
pnpm dev

# Build for production
pnpm build

# Preview production build
pnpm preview
```

## Learning Focus: useReducer + Context Best Practices

This project demonstrates several best practices when combining `useReducer` with React Context, following patterns from [Kent C. Dodds' "How to use React Context effectively"](https://kentcdodds.com/blog/how-to-use-react-context-effectively):

### 1. **Reducer Pattern with Typed Actions**

State updates are handled through a reducer with a discriminated union of actions:

```typescript
type Action =
| { type: 'ADD_ITEM'; payload: string }
| { type: 'UPDATE_ITEM'; payload: { productId: string; amount: number } };
```

This ensures type safety—TypeScript knows exactly which payload shape each action type expects.

### 2. **Context + useReducer Together**

Instead of managing state with `useState` in the provider, the app uses `useReducer`:

- **Reducer**: Pure function that handles all state transitions in one place
- **Context**: Provides both `state` and `dispatch` to any descendant component

This separation keeps logic predictable and makes it easy to add new actions without scattering state updates across components.

### 3. **Custom Hook with Provider Validation**

The `useShoppingCart()` hook wraps `useContext` and throws a clear error if used outside the provider:

```typescript
function useShoppingCart() {
const context = React.useContext(ShoppingCartContext);
if (!context) {
throw new Error('useShoppingCart must be used within a ShoppingCartProvider');
}
return context;
}
```

This prevents silent failures and gives developers immediate feedback when the provider is missing.

### 4. **Immutable State Updates**

The reducer always returns new state objects rather than mutating existing ones. Arrays are copied before modification (`[...state.items]`), and objects are spread (`{ ...existingCartItem }`). This aligns with React's expectations and enables proper re-renders.

### 5. **Exhaustive Action Handling**

The reducer's `default` case throws for unknown actions, catching typos and ensuring all action types are explicitly handled.

## Project Structure

```
src/
├── store/
│ └── shopping-cart-context.tsx # Context, reducer, provider, and hook
├── components/
│ ├── Header/ # Cart button with item count
│ ├── Shop/ # Product grid container
│ ├── Product/ # Individual product with "Add to Cart"
│ └── CartModal/
│ ├── index.tsx # Modal dialog wrapper
│ └── Cart/ # Cart contents and quantity controls
├── types/
│ └── product.ts
└── dummy-products.ts
```

## How It Works

1. **`ShoppingCartProvider`** wraps the app in `App.tsx`, making cart state available everywhere.
2. **`Product`** components dispatch `ADD_ITEM` when "Add to Cart" is clicked.
3. **`Header`** reads `state.items` to show the cart count and opens the cart modal.
4. **`Cart`** displays items and dispatches `UPDATE_ITEM` to change quantities (or remove items when quantity reaches 0).

## References

- [How to use React Context effectively](https://kentcdodds.com/blog/how-to-use-react-context-effectively) — Kent C. Dodds

## License

Private project for learning purposes.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@
<body>
<div id="modal"></div>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,19 @@
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0"
"react": "^19.2.3",
"react-dom": "^19.2.3"
},
"devDependencies": {
"@types/react": "^18.2.15",
"@types/react-dom": "^18.2.7",
"@vitejs/plugin-react": "^4.0.3",
"eslint": "^8.45.0",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.3",
"vite": "^4.4.5"
"@types/react": "^19.2.10",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.2",
"eslint": "^8.57.1",
"typescript": "^5.7.2",
"vite": "^6.4.1"
}
}
Loading