Skip to content

Commit ad9066b

Browse files
Initial commit
0 parents  commit ad9066b

23 files changed

Lines changed: 4052 additions & 0 deletions

.gitignore

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
yarn-debug.log*
6+
yarn-error.log*
7+
pnpm-debug.log*
8+
lerna-debug.log*
9+
10+
# Dependencies
11+
node_modules/
12+
13+
# Build output
14+
dist/
15+
dist-ssr/
16+
17+
# Local env files
18+
*.local
19+
20+
# Editor directories and files
21+
.vscode/*
22+
!.vscode/extensions.json
23+
.idea/
24+
.DS_Store
25+
*.suo
26+
*.ntvs*
27+
*.njsproj
28+
*.sln
29+
*.sw?

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 Mauricio Gómez (mgomez-dev-code)
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
# React Data Fetch + useMemo (Vite + TypeScript)
2+
3+
![React](https://img.shields.io/badge/React-18-61DAFB)
4+
![TypeScript](https://img.shields.io/badge/TypeScript-5-3178C6)
5+
![Vite](https://img.shields.io/badge/Vite-7-646CFF)
6+
![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)
7+
8+
A small React SPA that fetches product data from a public API and demonstrates **memoization of derived data** using `useMemo`.
9+
10+
This project focuses on **render behavior**, **performance awareness**, and **clean separation between data fetching, UI state, and derived state**.
11+
12+
---
13+
14+
## Features
15+
16+
- 🌐 **Data fetching** from a public REST API (`dummyjson.com`)
17+
- 🔎 **Client-side filtering** by:
18+
- Search term
19+
- Category
20+
- Sort option (title / price / rating)
21+
- 🧠 **Memoized derived data** using `useMemo`
22+
- Prevents unnecessary recomputation on theme changes
23+
- 🧩 Clear separation of concerns:
24+
- Data fetching (`useProducts`)
25+
- UI state (filters)
26+
- Derived state (filtered & sorted products)
27+
- 🌗 **Light / Dark theme toggle**
28+
- Implemented as unrelated UI state
29+
- Demonstrates that memoized derived data is NOT recomputed
30+
- 🎨 Clean, responsive layout using plain CSS Grid
31+
- ⚛️ React **StrictMode-friendly** (expected double renders in dev)
32+
33+
---
34+
35+
## Why this project exists
36+
37+
In React applications, it’s common to derive data from a base dataset
38+
(e.g. filtering, sorting, mapping).
39+
40+
This project demonstrates:
41+
42+
- **Why derived data should be memoized**
43+
- **When `useMemo` is useful**
44+
- How to avoid unnecessary recomputation when **unrelated UI state changes**
45+
- How unrelated UI state (such as a theme toggle) does not trigger expensive
46+
recomputations when derived data is properly memoized.
47+
- The difference between:
48+
- **Source state** (`products`)
49+
- **UI state** (search, category, sort)
50+
- **Derived state** (`visibleProducts`)
51+
52+
---
53+
54+
## Key Concepts Demonstrated
55+
56+
### `useMemo` for derived data
57+
58+
```ts
59+
const visibleProducts = useMemo(() => {
60+
let result = products;
61+
62+
if (searchTerm.trim()) {
63+
result = result.filter((p) =>
64+
p.title.toLowerCase().includes(searchTerm.toLowerCase()),
65+
);
66+
}
67+
68+
if (category !== "all") {
69+
result = result.filter((p) => p.category === category);
70+
}
71+
72+
return [...result].sort(/* sorting logic */);
73+
}, [products, searchTerm, category, sortBy]);
74+
```
75+
76+
- The computation only runs **when its dependencies change**
77+
- Unrelated state changes do **not** trigger recomputation
78+
- This scales much better as datasets grow
79+
80+
---
81+
82+
### Memoizing derived lists (categories)
83+
84+
```ts
85+
const categories = useMemo(() => {
86+
const unique = new Set(products.map((p) => p.category));
87+
return ["all", ...Array.from(unique)];
88+
}, [products]);
89+
```
90+
91+
- Categories depend **only** on fetched data
92+
- They are computed once per data load
93+
- No recomputation on UI interactions
94+
95+
---
96+
97+
## Project Structure
98+
99+
```
100+
react-data-fetch-memo/
101+
├─ src/
102+
│ ├─ api/
103+
│ │ └─ productsApi.ts
104+
│ ├─ components/
105+
│ │ ├─ Filters.tsx
106+
│ │ ├─ ProductList.tsx
107+
│ │ └─ ProductItem.tsx
108+
│ ├─ hooks/
109+
│ │ └─ useProducts.ts
110+
│ ├─ types/
111+
│ │ └─ product.ts
112+
│ ├─ App.tsx
113+
│ └─ main.tsx
114+
├─ public/
115+
├─ index.html
116+
└─ README.md
117+
```
118+
119+
---
120+
121+
## Getting Started
122+
123+
```bash
124+
npm install
125+
npm run dev
126+
# open http://localhost:5173
127+
```
128+
129+
### Production build
130+
131+
```bash
132+
npm run build
133+
npm run preview
134+
```
135+
136+
---
137+
138+
## Notes on React StrictMode
139+
140+
- In development, React StrictMode **intentionally double-invokes renders**
141+
- This is expected and helps surface unsafe side effects
142+
- **Production builds render only once**
143+
144+
This project is StrictMode-safe.
145+
146+
---
147+
148+
## Next Steps / Possible Extensions
149+
150+
- Extract filter logic into a custom hook
151+
- Add pagination or virtualization for large datasets
152+
- Add unit tests for filtering logic
153+
154+
---
155+
156+
This project is intentionally small and focused, aiming to demonstrate
157+
render behavior and memoization patterns rather than feature completeness.
158+
159+
---
160+
161+
## License
162+
163+
This project is licensed under the **MIT License**.

eslint.config.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import js from '@eslint/js'
2+
import globals from 'globals'
3+
import reactHooks from 'eslint-plugin-react-hooks'
4+
import reactRefresh from 'eslint-plugin-react-refresh'
5+
import tseslint from 'typescript-eslint'
6+
import { defineConfig, globalIgnores } from 'eslint/config'
7+
8+
export default defineConfig([
9+
globalIgnores(['dist']),
10+
{
11+
files: ['**/*.{ts,tsx}'],
12+
extends: [
13+
js.configs.recommended,
14+
tseslint.configs.recommended,
15+
reactHooks.configs.flat.recommended,
16+
reactRefresh.configs.vite,
17+
],
18+
languageOptions: {
19+
ecmaVersion: 2020,
20+
globals: globals.browser,
21+
},
22+
},
23+
])

index.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<title>react-data-fetch-memo</title>
8+
</head>
9+
<body>
10+
<div id="root"></div>
11+
<script type="module" src="/src/main.tsx"></script>
12+
</body>
13+
</html>

0 commit comments

Comments
 (0)