This chapter covers lifting state up, context, reducers, data fetching and forms.
Lifting state up
When two components need the same data, move the state to their closest common parent and pass the value and a setter down as props, so the data has a single source of truth.
import { useState } from "react";
function TemperatureInput({ value, onChange }: { value: number; onChange: (v: number) => void }) {
return <input type="number" value={value} onChange={(e) => onChange(Number(e.target.value))} />;
}
export function Thermostat() {
const [celsius, setCelsius] = useState(20);
return (
<div>
<TemperatureInput value={celsius} onChange={setCelsius} />
<p>{(celsius * 9) / 5 + 32} °F</p>
</div>
);
}
Passing data deeply with context
Passing values like the theme through many levels of props (prop drilling) gets tedious. Create a context with createContext, provide a value near the top, and any component below can read it with useContext. Current React renders the context itself as the provider; older code uses <ThemeContext.Provider>.
import { createContext, useContext } from "react";
type Theme = "light" | "dark";
const ThemeContext = createContext<Theme>("light");
export function App({ theme }: { theme: Theme }) {
return (
<ThemeContext value={theme}>
<Toolbar />
</ThemeContext>
);
}
function Toolbar() {
const theme = useContext(ThemeContext);
return <div className={`toolbar toolbar-${theme}`}>Theme: {theme}</div>;
}
Components reading a context re-render when its value changes, so split contexts by purpose.
Centralizing updates with useReducer
useReducer gathers many kinds of updates into one function: components dispatch actions describing what happened, and the reducer computes the next state. A discriminated union type for actions catches unhandled cases.
import { useReducer } from "react";
type Todo = { id: number; text: string; done: boolean };
type Action =
| { type: "added"; text: string }
| { type: "toggled"; id: number };
function todosReducer(todos: Todo[], action: Action): Todo[] {
switch (action.type) {
case "added":
return [...todos, { id: Date.now(), text: action.text, done: false }];
case "toggled":
return todos.map((t) => (t.id === action.id ? { ...t, done: !t.done } : t));
}
}
export function useTodos() {
const [todos, dispatch] = useReducer(todosReducer, []);
return { todos, add: (text: string) => dispatch({ type: "added", text }), dispatch };
}
A reducer plus context covers global state in small apps; larger apps may benefit from Zustand or Redux Toolkit.
Fetching server data
You can call fetch inside an effect, but abort the request in the cleanup function to avoid race conditions between responses.
0 則留言
登入 · 登入後即可留言。