![[Pasted image 20240609154853.png]] ![[Pasted image 20240609155252.png]]
Keeping Components Pure
To keep pure, we should keep changes out of the render phase.
![[Pasted image 20240609155801.png]]
1
2
3
4
5
6
7
8
9
10
11
12
13
14 import { useState, useEffect } from "react";
const Message = () => {
const [count, setCount] = useState(0);
// Optional: to increment count whenever the component is rendered or updated
useEffect(() => {
setCount(count + 1);
}, []);
return <div>Message {count}</div>;
};
export default Message;
Updating Objects
We should treat state objects as immutable and read only.
We don't modify any of the properties of the string object.
To tell React to update states, we have to give React a brand new object.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 function App() {
// It is better to group related state variabls inside an object.
const [drink, setDrink] = useState({
title: "Americano",
price: 6,
});
const handleClick = () => {
const newDrink = {
// use the spread operator
...drink,
price: 5,
};
setDrink(newDrink);
=>
setDrink({ ...drink, price:5 })
};
return (
<div>
<button onClick={handleClick}>Click me</button>
</div>
);
}
export default App;
Updating Nested Objects
In React, state updates should be done immutably, meaning you should not directly modify the existing state object. Instead, you should create a new object with the updated values.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 function App() {
const [customer, setCustomer] = useState({
name: "John",
address: {
city: "San Francisco",
zipCode: 94111,
},
});
const handleClick = () => {
setCustomer({
...customer,
address: { ...customer.address, zipCode: 94112 },
});
};
return (
<div>
<button onClick={handleClick}>Click me</button>
</div>
);
}
export default App;
Spread Operator :
...customer
creates a shallow copy of the customer
object.
address: { ...customer.address, zipCode: 94112 }
creates a shallow copy of the address
object and updates the zipCode
.
Creating New Objects :
By using the spread operator, you are not mutating the original customer
object or its address
property. Instead, you create new copies of these objects with the updated values.
Updating State :
setCustomer
is called with the new object, ensuring that React detects the state change due to the new reference.
Updating Array
Updating arrays in React follows the same immutability principles as updating objects. When updating an array, you should create a new array with the updated values rather than modifying the existing array directly. This ensures React can properly detect changes and trigger re-renders.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35 import React, { useState } from 'react';
const TagsComponent = () => {
const [tags, setTags] = useState(['happy', 'cheerful']);
// Add a new tag
const addTag = (newTag) => {
setTags([...tags, newTag]);
};
// Remove a tag by name
const removeTag = (tagName) => {
setTags(tags.filter((tag) => tag !== tagName));
};
// Update a tag by name
const updateTag = (oldTag, newTag) => {
setTags(tags.map((tag) => (tag === oldTag ? newTag : tag)));
};
return (
<div>
<ul>
{tags.map((tag, index) => (
<li key={index}>{tag}</li>
))}
</ul>
<button onClick={() => addTag('excited')}>Add Tag</button>
<button onClick={() => removeTag('happy')}>Remove 'happy'</button>
<button onClick={() => updateTag('cheerful', 'joyful')}>Update 'cheerful' to 'joyful'</button>
</div>
);
};
export default TagsComponent;
Updating Array of Objects
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40 import React, { useState } from 'react';
const TagsComponent = () => {
const [tags, setTags] = useState([
{ id: 1, name: 'happy' },
{ id: 2, name: 'cheerful' }
]);
// Add a new tag
const addTag = (newTag) => {
setTags([...tags, newTag]);
};
// Remove a tag by name
const removeTag = (tagName) => {
setTags(tags.filter(tag => tag.name !== tagName));
};
// Update a tag by name
const updateTag = (oldTagName, newTagName) => {
setTags(tags.map(tag =>
tag.name === oldTagName ? { ...tag, name: newTagName } : tag
));
};
return (
<div>
<ul>
{tags.map(tag => (
<li key={tag.id}>{tag.name}</li>
))}
</ul>
<button onClick={() => addTag({ id: tags.length + 1, name: 'excited' })}>Add Tag</button>
<button onClick={() => removeTag('happy')}>Remove 'happy'</button>
<button onClick={() => updateTag('cheerful', 'joyful')}>Update 'cheerful' to 'joyful'</button>
</div>
);
};
export default TagsComponent;
Summary of Updating Objects and Array
Objects
const [drink, setDrink] = useState({
title: "Americano",
price: 6,
});
...
setDrink({ ...drink, price: 5 })
Nested Objects
1
2
3
4
5
6
7
8
9
10
11
12 const [customer, setCustomer] = useState({
name: "John",
address: {
city: "San Francisco",
zipCode: 94111,
},
});
setCustomer({
...customer,
address: { ...customer.address, zipCode: 94112 },
})
Array
const [tags, setTags] = useState(['happy', 'cheerful']);
// 增
setTags([...tags, 'exciting']);
// 删
setTags(tags.filter((tag) => tag !== 'happy'))
// 改
setTags(tags.map((tag) => tag === 'happy' ? 'happiness' : tag)
Array of Objects
const [tags, setTags] = useState([
{ id: 1, name: 'happy' },
{ id: 2, name: 'cheerful' }
]);
// 增(还是一样的)
setTags([ ...tags, { id: 3, name: 'exciting' } ])
// 删(删去 id 为 1 的 tag)
setTags(tags.filter(tag => tag.id !== 1)
// 改(修改 id 为 1 的 tag 的 name)
setTags(tags.map(tag.id === 1 ? { ...tags, tag.name: 'sad' } : tag.name))
Simplify Update Logic with Immer
npm install immer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 import produce from 'immer'
setBugs(produce(draft => {
const bug = draft.find(bug => bug.id === 1)
if (bug) bug.fixed = true
}))
````
## Sharing States between Components
Sometimes, you want the state of two components to always change together. To do it, remove state from both of them, ==move it to their closest common parent==, and then ==pass it down to them via props==. This is known as **lifting state up**, and it’s one of the most common things you will do writing React code.
`App.tsx`
```tsx
import Cart from "./components/Cart";
import NavBar from "./components/NavBar";
import { useState } from "react";
function App() {
const [cartItems, setCartItems] = useState(["product 1", "product 2"]);
return (
<div>
<NavBar cartItemsCount={cartItems.length} />
<Cart cartItems={cartItems} onClear={() => setCartItems([])} />
<button onClick={handleClick}>Click me</button>
</div>
);
}
export default App;
NavBar.tsx
interface Props {
cartItemsCount: number;
}
const NavBar = ({ cartItemsCount }: Props) => {
return <div>NavBar: {cartItemsCount}</div>;
};
export default NavBar;
Cart.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 interface Props {
cartItems: string[];
onClear: () => void;
}
const Cart = ({ cartItems, onClear }: Props) => {
return (
<div>
{cartItems.map((item, index) => (
<li key={index}>{item}</li>
))}
<button onClick={onClear}>Clear</button>
</div>
);
};
export default Cart;
Exercise - Updating State
Object
const [game, setGame] = useState({
id: 1,
player: {
name: "John",
},
});
const handleClick = () => {
setGame({ ...game, player: { ...game.player, name: "Bob" } });
Array
const [pizza, setPizza] = useState({
name: "Spicy Pepperoni",
toppings: ["Mushroom"],
});
const handleClick = () => {
// 增加一个 toppings
setPizza({ ...pizza, toppings: [...pizza.toppings, "pineapple"] });
};
Nested Object
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 const [cart, setCart] = useState({
discount: 0.1,
items: [
{ id: 1, title: "Product 1", quantity: 1 },
{ id: 2, title: "Product 2", quantity: 1 },
],
});
const handleClick = () =>
setCart({
...cart,
items: cart.items.map((item) =>
item.id === 1 ? { ...item, quantity: 2 } : item
),
});
// Summary:
❌ setCart({...cart, items: [cart.items.map((item) => item.id === 1 ? item.quantity = 2 : item )]})
✔️ setCart({...cart, items: [cart.items.map((item) => item.id === 1 ? {...item, quantity: 2} : item )]})
Exercise - Expandable Text Component
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 import { useState } from "react";
interface Props {
children: string;
maxChars?: number;
}
const ExpandableText = ({ children, maxChars = 100 }: Props) => {
const [isExpanded, setExpaned] = useState(false);
if (children.length < maxChars) return children;
return (
<>
<p>
{isExpanded ? children : children.slice(0, maxChars) + "..."}
<button onClick={() => setExpaned(!isExpanded)}>
{isExpanded ? "Less" : "More"}
</button>
</p>
</>
);
};
export default ExpandableText;
2024-06-09 15:39 2024-06-10 18:56