Skip to content

Commit b161c8d

Browse files
committed
Completed exercise jonasschmedtmann#11: World Wise Optimizing Bundle Size in section 19 of the course
1 parent b68e9ac commit b161c8d

File tree

7 files changed

+294
-76
lines changed

7 files changed

+294
-76
lines changed

11-worldwise/starter/src/App.jsx

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,49 @@
1-
import { useEffect, useState } from "react";
1+
import { lazy, Suspense } from "react";
22
import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom";
3+
import { CitiesProvider } from "./contexts/CitiesContext.jsx";
4+
import { AuthProvider } from "./contexts/FakeAuthContext.jsx";
35

4-
import Homepage from "./pages/Homepage/Homepage";
5-
import Products from "./pages/Product/Products";
6-
import Pricing from "./pages/Product/Pricing";
7-
import Login from "./pages/Login/Login";
8-
import AppLayout from "./pages/AppLayout/AppLayout";
9-
import PageNotFound from "./pages/PageNotFound";
106
import ProtectedRoute from "./pages/ProtectedRoute";
117
import CityList from "./components/City/CityList";
128
import CountryList from "./components/Country/CountryList";
139
import City from "./components/City/City";
1410
import Form from "./components/Form/Form";
11+
import SpinnerFullPage from "./components/Spinner/SpinnerFullPage";
12+
13+
// import Homepage from "./pages/Homepage/Homepage";
14+
// import Products from "./pages/Product/Products";
15+
// import Pricing from "./pages/Product/Pricing";
16+
// import Login from "./pages/Login/Login";
17+
// import AppLayout from "./pages/AppLayout/AppLayout";
18+
// import PageNotFound from "./pages/PageNotFound";
19+
20+
const Homepage = lazy(()=> import("./pages/Homepage/Homepage"))
21+
const Products = lazy(()=> import("./pages/Product/Products"))
22+
const Pricing = lazy(()=> import("./pages/Product/Pricing"))
23+
const Login= lazy(()=> import("./pages/Login/Login"))
24+
const AppLayout= lazy(()=> import("./pages/AppLayout/AppLayout"))
25+
const PageNotFound= lazy(()=> import("./pages/PageNotFound"))
1526

16-
import { CitiesProvider } from "./contexts/CitiesContext.jsx";
17-
import { AuthProvider } from "./contexts/FakeAuthContext.jsx";
1827

1928
const App = () => {
2029
return (
21-
<AuthProvider>
22-
<CitiesProvider>
30+
<AuthProvider>
31+
<CitiesProvider>
2332
<BrowserRouter>
33+
<Suspense fallback={<SpinnerFullPage/>}>
2434
<Routes>
2535
<Route index element={<Homepage />} />
2636
<Route path="products" element={<Products />} />
2737
<Route path="pricing" element={<Pricing />} />
2838
<Route path="login" element={<Login />} />
29-
<Route path="app" element={
30-
<ProtectedRoute>
31-
32-
<AppLayout />
33-
</ProtectedRoute>
34-
35-
}>
39+
<Route
40+
path="app"
41+
element={
42+
<ProtectedRoute>
43+
<AppLayout />
44+
</ProtectedRoute>
45+
}
46+
>
3647
<Route
3748
index
3849
element={<Navigate replace to="cities" />}
@@ -44,9 +55,10 @@ const App = () => {
4455
</Route>
4556
<Route path="*" element={<PageNotFound />} />
4657
</Routes>
58+
</Suspense>
4759
</BrowserRouter>
48-
</CitiesProvider>
49-
</AuthProvider>
60+
</CitiesProvider>
61+
</AuthProvider>
5062
);
5163
};
5264

11-worldwise/starter/src/components/City/City.jsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,12 @@ const formatDate = date =>
1717
function City() {
1818
const { getCity, currentCity, isLoading } = useCities();
1919
const { id } = useParams();
20-
2120

2221
const { cityName, emoji, date, notes } = currentCity;
2322

2423
useEffect(() => {
2524
getCity(id);
26-
}, [id]);
25+
}, [id, getCity]);
2726

2827
if (isLoading) return <Spinner />;
2928

11-worldwise/starter/src/contexts/CitiesContext.jsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { createContext, useEffect, useContext, useReducer } from "react";
1+
import { useCallback,createContext, useEffect, useContext, useReducer } from "react";
22

33
const BASE_URL = "http://localhost:9000";
44

@@ -56,7 +56,7 @@ function CitiesProvider({ children }) {
5656
initialState
5757
);
5858

59-
async function getCity(id) {
59+
const getCity = useCallback(async function getCity(id) {
6060
if (id === String(currentCity.id)) return;
6161
dispatch({ type: "loading" });
6262

@@ -70,7 +70,7 @@ function CitiesProvider({ children }) {
7070
payload: "There was an error loading the city..."
7171
});
7272
}
73-
}
73+
}, [currentCity.id])
7474

7575
async function createCity(newCity) {
7676
dispatch({ type: "loading" });
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
import { useCallback, memo, useMemo, useEffect, useState } from "react";
2+
import { faker } from "@faker-js/faker";
3+
4+
function createRandomPost() {
5+
return {
6+
title: `${faker.hacker.adjective()} ${faker.hacker.noun()}`,
7+
body: faker.hacker.phrase()
8+
};
9+
}
10+
11+
function App() {
12+
const [posts, setPosts] = useState(() =>
13+
Array.from({ length: 30 }, () => createRandomPost())
14+
);
15+
const [searchQuery, setSearchQuery] = useState("");
16+
const [isFakeDark, setIsFakeDark] = useState(false);
17+
18+
// Derived state. These are the posts that will actually be displayed
19+
const searchedPosts =
20+
searchQuery.length > 0
21+
? posts.filter(post =>
22+
`${post.title} ${post.body}`
23+
.toLowerCase()
24+
.includes(searchQuery.toLowerCase())
25+
)
26+
: posts;
27+
28+
const handleAddPost = useCallback(
29+
function handleAddPost(post) {
30+
setPosts(posts => [post, ...posts]);
31+
console.log(posts);
32+
},
33+
[posts]
34+
);
35+
36+
function handleClearPosts() {
37+
setPosts([]);
38+
}
39+
40+
// Whenever `isFakeDark` changes, we toggle the `fake-dark-mode` class on the HTML element (see in "Elements" dev tool).
41+
useEffect(
42+
function () {
43+
document.documentElement.classList.toggle("fake-dark-mode");
44+
},
45+
[isFakeDark]
46+
);
47+
48+
const archiveOption = useMemo(() => {
49+
return { show: false, title: `Post archive ${posts.length}` };
50+
}, [posts.length]);
51+
52+
return (
53+
<section>
54+
<button
55+
onClick={() => setIsFakeDark(isFakeDark => !isFakeDark)}
56+
className="btn-fake-dark-mode"
57+
>
58+
{isFakeDark ? "☀️" : "🌙"}
59+
</button>
60+
61+
<Header
62+
posts={searchedPosts}
63+
onClearPosts={handleClearPosts}
64+
searchQuery={searchQuery}
65+
setSearchQuery={setSearchQuery}
66+
/>
67+
<Main posts={searchedPosts} onAddPost={handleAddPost} />
68+
<Archive
69+
archiveOption={archiveOption}
70+
onAddPost={handleAddPost}
71+
setIsFakeDark={setIsFakeDark}
72+
/>
73+
<Footer />
74+
</section>
75+
);
76+
}
77+
78+
function Header({ posts, onClearPosts, searchQuery, setSearchQuery }) {
79+
return (
80+
<header>
81+
<h1>
82+
<span>⚛️</span>The Atomic Blog
83+
</h1>
84+
<div>
85+
<Results posts={posts} />
86+
<SearchPosts
87+
searchQuery={searchQuery}
88+
setSearchQuery={setSearchQuery}
89+
/>
90+
<button onClick={onClearPosts}>Clear posts</button>
91+
</div>
92+
</header>
93+
);
94+
}
95+
96+
function SearchPosts({ searchQuery, setSearchQuery }) {
97+
return (
98+
<input
99+
value={searchQuery}
100+
onChange={e => setSearchQuery(e.target.value)}
101+
placeholder="Search posts..."
102+
/>
103+
);
104+
}
105+
106+
function Results({ posts }) {
107+
return <p>🚀 {posts.length} atomic posts found</p>;
108+
}
109+
110+
function Main({ posts, onAddPost }) {
111+
return (
112+
<main>
113+
<FormAddPost onAddPost={onAddPost} />
114+
<Posts posts={posts} />
115+
</main>
116+
);
117+
}
118+
119+
function Posts({ posts }) {
120+
return (
121+
<section>
122+
<List posts={posts} />
123+
</section>
124+
);
125+
}
126+
127+
function FormAddPost({ onAddPost }) {
128+
const [title, setTitle] = useState("");
129+
const [body, setBody] = useState("");
130+
131+
const handleSubmit = function (e) {
132+
e.preventDefault();
133+
if (!body || !title) return;
134+
onAddPost({ title, body });
135+
setTitle("");
136+
setBody("");
137+
};
138+
139+
return (
140+
<form onSubmit={handleSubmit}>
141+
<input
142+
value={title}
143+
onChange={e => setTitle(e.target.value)}
144+
placeholder="Post title"
145+
/>
146+
<textarea
147+
value={body}
148+
onChange={e => setBody(e.target.value)}
149+
placeholder="Post body"
150+
/>
151+
<button>Add post</button>
152+
</form>
153+
);
154+
}
155+
156+
function List({ posts }) {
157+
return (
158+
<ul>
159+
{posts.map((post, i) => (
160+
<li key={i}>
161+
<h3>{post.title}</h3>
162+
<p>{post.body}</p>
163+
</li>
164+
))}
165+
</ul>
166+
);
167+
}
168+
169+
const Archive = memo(function Archive({ archiveOption, onAddPost }) {
170+
// Here we don't need the setter function. We're only using state to store these posts because the callback function passed into useState (which generates the posts) is only called once, on the initial render. So we use this trick as an optimization technique, because if we just used a regular variable, these posts would be re-created on every render. We could also move the posts outside the components, but I wanted to show you this trick 😉
171+
const [posts] = useState(() =>
172+
// 💥 WARNING: This might make your computer slow! Try a smaller `length` first
173+
Array.from({ length: 10000 }, () => createRandomPost())
174+
);
175+
176+
const [showArchive, setShowArchive] = useState(archiveOption.show);
177+
178+
return (
179+
<aside>
180+
<h2>{archiveOption.title}</h2>
181+
<button onClick={() => setShowArchive(s => !s)}>
182+
{showArchive ? "Hide archive posts" : "Show archive posts"}
183+
</button>
184+
185+
{showArchive && (
186+
<ul>
187+
{posts.map((post, i) => (
188+
<li key={i}>
189+
<p>
190+
<strong>{post.title}:</strong> {post.body}
191+
</p>
192+
<button onClick={() => onAddPost(post)}>
193+
Add as new post
194+
</button>
195+
</li>
196+
))}
197+
</ul>
198+
)}
199+
</aside>
200+
);
201+
});
202+
203+
function Footer() {
204+
return <footer>&copy; by The Atomic Blog ✌️</footer>;
205+
}
206+
207+
export default App;

12-atomic-blog/starter/src/App-v2.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useEffect, useState } from "react";
1+
import {memo, useEffect, useState } from "react";
22
import { faker } from "@faker-js/faker";
33
import { PostProvider, usePosts } from "./PostContext";
44
import Test from "./Test";
@@ -76,14 +76,14 @@ function Results() {
7676
return <p>🚀 {posts.length} atomic posts found</p>;
7777
}
7878

79-
function Main() {
79+
const Main = memo(function Main() {
8080
return (
8181
<main>
8282
<FormAddPost />
8383
<Posts />
8484
</main>
8585
);
86-
}
86+
})
8787

8888
function Posts() {
8989
return (

0 commit comments

Comments
 (0)