Skip to content

Commit e2e614e

Browse files
committed
finish exercise 2
1 parent 8534ff3 commit e2e614e

File tree

25 files changed

+1560
-5
lines changed

25 files changed

+1560
-5
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Init Callback
2+
3+
🦉 There's one more thing you should know about `useState` initialization and
4+
that is a small performance optimization. `useState` can accept a function.
5+
6+
You may recall from earlier we mentioned that the first argument to `useState`
7+
is only used during the initial render. It's not used on subsequent renders.
8+
This is because the initial value is only used when the component is first
9+
rendered. After that, the value is managed by React and you use the updater
10+
function to update it.
11+
12+
But imagine a situation where calculating that initial value were
13+
computationally expensive. It would be a waste to compute the initial value for
14+
all but the initial render right? That's where the function form of `useState`
15+
comes in.
16+
17+
Let's imagine we have a function that calculates the initial value and it's
18+
computationally expensive:
19+
20+
```tsx
21+
const [val, setVal] = useState(calculateInitialValue())
22+
```
23+
24+
This will work just fine, but it's not ideal. The `calculateInitialValue` will
25+
be called on every render, even though it's only needed for the initial render.
26+
So instead of calling the function, we can just pass it:
27+
28+
```tsx
29+
const [val, setVal] = useState(calculateInitialValue)
30+
```
31+
32+
Typically doing this is unnecessary, but it's good to know about in case you
33+
need it.
34+
35+
So
36+
37+
```tsx
38+
// both of these work just fine:
39+
const [query, setQuery] = useState(getQueryParam())
40+
const [query, setQuery] = useState(getQueryParam)
41+
```
42+
43+
You're going to be making the `getQueryParam` function. Got it? Great, let's go!
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
html,
2+
body {
3+
margin: 0;
4+
}
5+
6+
.app {
7+
margin: 40px auto;
8+
max-width: 1024px;
9+
form {
10+
text-align: center;
11+
}
12+
}
13+
14+
.post-list {
15+
list-style: none;
16+
padding: 0;
17+
display: flex;
18+
gap: 20px;
19+
flex-wrap: wrap;
20+
justify-content: center;
21+
li {
22+
position: relative;
23+
border-radius: 0.5rem;
24+
overflow: hidden;
25+
border: 1px solid #ddd;
26+
width: 320px;
27+
transition: transform 0.2s ease-in-out;
28+
a {
29+
text-decoration: none;
30+
color: unset;
31+
}
32+
33+
&:hover,
34+
&:has(*:focus),
35+
&:has(*:active) {
36+
transform: translate(0px, -6px);
37+
}
38+
39+
.post-image {
40+
display: block;
41+
width: 100%;
42+
height: 200px;
43+
}
44+
45+
button {
46+
position: absolute;
47+
font-size: 1.5rem;
48+
top: 20px;
49+
right: 20px;
50+
background: transparent;
51+
border: none;
52+
outline: none;
53+
&:hover,
54+
&:focus,
55+
&:active {
56+
animation: pulse 1.5s infinite;
57+
}
58+
}
59+
60+
a {
61+
padding: 10px 10px;
62+
display: flex;
63+
gap: 8px;
64+
flex-direction: column;
65+
h2 {
66+
margin: 0;
67+
font-size: 1.5rem;
68+
font-weight: bold;
69+
}
70+
p {
71+
margin: 0;
72+
font-size: 1rem;
73+
color: #666;
74+
}
75+
}
76+
}
77+
}
78+
79+
@keyframes pulse {
80+
0% {
81+
transform: scale(1);
82+
}
83+
50% {
84+
transform: scale(1.3);
85+
}
86+
100% {
87+
transform: scale(1);
88+
}
89+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import { useState } from 'react'
2+
import * as ReactDOM from 'react-dom/client'
3+
import { generateGradient, getMatchingPosts } from '#shared/blog-posts'
4+
5+
// 🐨 make a function here called getQueryParam
6+
7+
function App() {
8+
// 🐨 move 👇 up to getQueryParam
9+
const params = new URLSearchParams(window.location.search)
10+
const initialQuery = params.get('query') ?? ''
11+
// 🐨 move 👆 up to getQueryParam and return the initialQuery
12+
13+
// 🐨 pass getQueryParam into useState
14+
const [query, setQuery] = useState(initialQuery)
15+
const words = query.split(' ')
16+
17+
const dogChecked = words.includes('dog')
18+
const catChecked = words.includes('cat')
19+
const caterpillarChecked = words.includes('caterpillar')
20+
21+
function handleCheck(tag: string, checked: boolean) {
22+
const newWords = checked ? [...words, tag] : words.filter(w => w !== tag)
23+
setQuery(newWords.filter(Boolean).join(' ').trim())
24+
}
25+
26+
return (
27+
<div className="app">
28+
<form>
29+
<div>
30+
<label htmlFor="searchInput">Search:</label>
31+
<input
32+
id="searchInput"
33+
name="query"
34+
type="search"
35+
value={query}
36+
onChange={e => setQuery(e.currentTarget.value)}
37+
/>
38+
</div>
39+
<div>
40+
<label>
41+
<input
42+
type="checkbox"
43+
checked={dogChecked}
44+
onChange={e => handleCheck('dog', e.currentTarget.checked)}
45+
/>{' '}
46+
🐶 dog
47+
</label>
48+
<label>
49+
<input
50+
type="checkbox"
51+
checked={catChecked}
52+
onChange={e => handleCheck('cat', e.currentTarget.checked)}
53+
/>{' '}
54+
🐱 cat
55+
</label>
56+
<label>
57+
<input
58+
type="checkbox"
59+
checked={caterpillarChecked}
60+
onChange={e =>
61+
handleCheck('caterpillar', e.currentTarget.checked)
62+
}
63+
/>{' '}
64+
🐛 caterpillar
65+
</label>
66+
</div>
67+
<button type="submit">Submit</button>
68+
</form>
69+
<MatchingPosts query={query} />
70+
</div>
71+
)
72+
}
73+
74+
function MatchingPosts({ query }: { query: string }) {
75+
const matchingPosts = getMatchingPosts(query)
76+
77+
return (
78+
<ul className="post-list">
79+
{matchingPosts.map(post => (
80+
<li key={post.id}>
81+
<div
82+
className="post-image"
83+
style={{ background: generateGradient(post.id) }}
84+
/>
85+
<a
86+
href={post.id}
87+
onClick={event => {
88+
event.preventDefault()
89+
alert(`Great! Let's go to ${post.id}!`)
90+
}}
91+
>
92+
<h2>{post.title}</h2>
93+
<p>{post.description}</p>
94+
</a>
95+
</li>
96+
))}
97+
</ul>
98+
)
99+
}
100+
101+
const rootEl = document.createElement('div')
102+
document.body.append(rootEl)
103+
ReactDOM.createRoot(rootEl).render(<App />)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Init Callback
2+
3+
👨‍💼 Great! This isn't 100% necessary as a performance optimization, but it's easy
4+
and doesn't hurt readability so we may as well!
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
html,
2+
body {
3+
margin: 0;
4+
}
5+
6+
.app {
7+
margin: 40px auto;
8+
max-width: 1024px;
9+
form {
10+
text-align: center;
11+
}
12+
}
13+
14+
.post-list {
15+
list-style: none;
16+
padding: 0;
17+
display: flex;
18+
gap: 20px;
19+
flex-wrap: wrap;
20+
justify-content: center;
21+
li {
22+
position: relative;
23+
border-radius: 0.5rem;
24+
overflow: hidden;
25+
border: 1px solid #ddd;
26+
width: 320px;
27+
transition: transform 0.2s ease-in-out;
28+
a {
29+
text-decoration: none;
30+
color: unset;
31+
}
32+
33+
&:hover,
34+
&:has(*:focus),
35+
&:has(*:active) {
36+
transform: translate(0px, -6px);
37+
}
38+
39+
.post-image {
40+
display: block;
41+
width: 100%;
42+
height: 200px;
43+
}
44+
45+
button {
46+
position: absolute;
47+
font-size: 1.5rem;
48+
top: 20px;
49+
right: 20px;
50+
background: transparent;
51+
border: none;
52+
outline: none;
53+
&:hover,
54+
&:focus,
55+
&:active {
56+
animation: pulse 1.5s infinite;
57+
}
58+
}
59+
60+
a {
61+
padding: 10px 10px;
62+
display: flex;
63+
gap: 8px;
64+
flex-direction: column;
65+
h2 {
66+
margin: 0;
67+
font-size: 1.5rem;
68+
font-weight: bold;
69+
}
70+
p {
71+
margin: 0;
72+
font-size: 1rem;
73+
color: #666;
74+
}
75+
}
76+
}
77+
}
78+
79+
@keyframes pulse {
80+
0% {
81+
transform: scale(1);
82+
}
83+
50% {
84+
transform: scale(1.3);
85+
}
86+
100% {
87+
transform: scale(1);
88+
}
89+
}

0 commit comments

Comments
 (0)