Notebook in React - 7/22/2024
Best practices and good tips
How to choose the state structure
import { useState } from 'react';
const initialItems = [
{ title: 'pretzels', id: 0 },
{ title: 'crispy seaweed', id: 1 },
{ title: 'granola bar', id: 2 },
];
export default function Menu() {
const [items, setItems] = useState(initialItems);
const [selectedItem, setSelectedItem] = useState(
items[0]
);
function handleItemChange(id, e) {
setItems(items.map(item => {
if (item.id === id) {
return {
...item,
title: e.target.value,
};
} else {
return item;
}
}));
}
return (
<>
<h2>What's your travel snack?</h2>
<ul>
{items.map((item, index) => (
<li key={item.id}>
<input
value={item.title}
onChange={e => {
handleItemChange(item.id, e)
}}
/>
{' '}
<button onClick={() => {
setSelectedItem(item);
}}>Choose</button>
</li>
))}
</ul>
<p>You picked {selectedItem.title}.</p>
</>
);
}
The state used to be duplicated like this:
items = [{ id: 0, title: 'pretzels'}, ...]
selectedItem = {id: 0, title: 'pretzels'}
Currently, it stores the selected item as an object in the selectedItem state variable. However, this is not great: the contents of the selectedItem is the same object as one of the items inside the items list. This means that the information about the item itself is duplicated in two places.
Notice how if you first click “Choose” on an item and then edit it, the input updates but the label at the bottom does not reflect the edits. This is because you have duplicated state, and you forgot to update selectedItem.
What's your travel snack?
You picked pretzels.
Although you could update selectedItem too, an easier fix is to remove duplication. In this example, instead of a selectedItem object (which creates a duplication with objects inside items), you hold the selectedId in state, and then get the selectedItem by searching the items array for an item with that ID:
import { useState } from 'react';
const initialItems = [
{ title: 'pretzels', id: 0 },
{ title: 'crispy seaweed', id: 1 },
{ title: 'granola bar', id: 2 },
];
export default function Menu() {
const [items, setItems] = useState(initialItems);
const [selectedId, setSelectedId] = useState(0);
const selectedItem = items.find(item =>
item.id === selectedId
);
function handleItemChange(id, e) {
setItems(items.map(item => {
if (item.id === id) {
return {
...item,
title: e.target.value,
};
} else {
return item;
}
}));
}
return (
<>
<h2>What's your travel snack?</h2>
<ul>
{items.map((item, index) => (
<li key={item.id}>
<input
value={item.title}
onChange={e => {
handleItemChange(item.id, e)
}}
/>
{' '}
<button onClick={() => {
setSelectedId(item.id);
}}>Choose</button>
</li>
))}
</ul>
<p>You picked {selectedItem.title}.</p>
</>
);
}
State change like this:
items = [{ id: 0, title: 'pretzels'}, ...]
selectedId = 0
The duplication is gone, and you only keep the essential state!
Now if you edit the selected item, the message below will update immediately. This is because setItems triggers a re-render, and items.find(…) would find the item with the updated title. You didn’t need to hold the selected item in state, because only the selected ID is essential. The rest could be calculated during render.
what is the different between value and default value in React forms?
In React forms, value and defaultValue serve different purposes for managing the state of form text inputs.
“value”
- Controlled Component: When you use value, you are creating a controlled component. This means that the form input’s value is controlled by React state.
- Two-way Binding: The value of the input is directly tied to the component’s state. To update the input, you need to update the state.
- Usage: when you need to have complete control over the input’s value, such as for form validation or dynamic updates.
“defaultValue”
- Uncontrolled Component: When you use defaultValue, you are creating an uncontrolled component. The initial value is set, but React does not control its subsequent updates.
- Initial Value: The input gets an initial value, but it does not update based on state changes. Instead, the DOM itself maintains the value.
- Usage: Use defaultValue when you only need to set an initial value and don’t need to control the input’s state after the initial render.
purity vs side effect ?
In react,for keeping component pure, You should not mutate any of the inputs that your components use for rendering. That includes props, state, and context. To update the screen, “set” state instead of mutating preexisting objects.
While functional programming relies heavily on purity,at some point, somewhere, something has to change. That’s kind of the point of programming! These changes—updating the screen, starting an animation, changing the data—are called side effects. They’re things that happen “on the side”, not during rendering.
For example, if you need to use a prop as the initial value of a component’s state and potentially change that state later, you can do this safely by copying the prop’s value into the component’s state.
Here’s an example of how this can be done:
import React, { useState, useEffect } from 'react';
function MyComponent({ initialValue }) {
// Initialize state with the prop value
const [value, setValue] = useState(initialValue);
// This effect will updates the state with the new value. This ensures that your state reflects any updates to the prop after the component has been mounted.
useEffect(() => {
setValue(initialValue);
}, [initialValue]);
// handle function can update the state independently of the prop, allowing your component to manage its own state internally.
const handleChange = (newValue) => {
setValue(newValue);
};
return (
<div>
<p>Current Value: {value}</p>
<button onClick={() => handleChange(value + 1)}>Increment</button>
</div>
);
}
export default MyComponent;
Remove unnecessary state
When the button is clicked, this example should ask for the user’s name and then display an alert greeting them. You tried to use state to keep the name, but for some reason it shows “Hello, !“.
import { useState } from 'react';
export default function FeedbackForm() {
const [name, setName] = useState('');
function handleClick() {
setName(prompt('What is your name?'));
alert(`Hello, ${name}!`);
}
return (
<button onClick={handleClick}>
Greet
</button>
);
}
A state variable’s value never changes within a render State Update is Asynchronous: When you call setName, React schedules a state update, but it doesn’t update name immediately within the same function execution. This is because React batches state updates to optimize rendering.
To fix this, you can modify your handleClick function to use the value directly from the prompt instead of relying on the state immediately after setting it:
import { useState } from 'react';
export default function FeedbackForm() {
const [name, setName] = useState('');
function handleClick() {
const newName = prompt('What is your name?');
setName(newName);
alert(`Hello, ${newName}!`);
}
return (
<button onClick={handleClick}>
Greet
</button>
);
}
- newName variable: This variable stores the value returned from prompt.
- setName(newName): This updates the state with the new name.
- alert uses newName: The alert uses the newName directly, ensuring it reflects the value just entered by the user.
If your only goal is to show the name immediately after the user inputs it, and you don’t need to store the name for later use in the component, then you don’t need to use React state (useState) at all.Just variable is enough.
export default function FeedbackForm() {
function handleClick() {
const name = prompt('What is your name?');
alert(`Hello, ${name}!`);
}
return (
<button onClick={handleClick}>
Greet
</button>
);
}s
You only need to use state when you want to retain some data across renders or use it in the rendering logic of your component.
TypeScript
In TypeScript, Record<string, string>
is a utility type that defines an object where the keys are of type string and the values are also of type string.
Record<string, string>
// This utility type is equivalent to defining an object like this:
type MyObject = {
[key: string]: string;
};
// example:
const user: Record<string, string> = {
name: "Alice",
age: "25", // Even though this is typically a number, here it's a string.
city: "New York"
};
console.log(user.name); // Output: Alice
console.log(user.age); // Output: 25
Use Cases:
- Mapping dynamic data: When you’re unsure of the exact keys ahead of time, but you know that both the keys and values will be strings.
- Restricting key-value pairs: Ensures that all keys and values are strings, providing more type safety in TypeScript.