React immediate tour part 1
Built-in React Hooks
Hooks let you use different React features from your components. You can either use the built-in Hooks or combine them to build your own.
State Hooks
State lets a component remember information like user input.
useState
: declares a state variable that you can update directlyuseReducer
: declare a state variable with the update logic inside a reducer function
function ImageGallery() {
const [index, setIndex] = useState(0);
// ...
}
Extracting State Logic into a Reducer
Component with many state updates spread cross many event handlers can become overwhelming. For these cases, you can consolidate all the state update logic outside your component in a single function, call a reducer.
Example:
import { useReducer } from 'react';
import AddTask from './AddTask.js';
import TaskList from './TaskList.js';
export default function TaskApp() {
const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);
function handleAddTask(text) {
dispatch({
type: 'added',
id: nextId++,
text: text,
});
}
function handleChangeTask(task) {
dispatch({
type: 'changed',
task: task,
});
}
function handleDeleteTask(taskId) {
dispatch({
type: 'deleted',
id: taskId,
});
}
return (
<>
<h1>Prague itinerary</h1>
<AddTask onAddTask={handleAddTask} />
<TaskList
tasks={tasks}
onChangeTask={handleChangeTask}
onDeleteTask={handleDeleteTask}
/>
</>
);
}
function tasksReducer(tasks, action) {
switch (action.type) {
case 'added': {
return [
...tasks,
{
id: action.id,
text: action.text,
done: false,
},
];
}
case 'changed': {
return tasks.map((t) => {
if (t.id === action.task.id) {
return action.task;
} else {
return t;
}
});
}
case 'deleted': {
return tasks.filter((t) => t.id !== action.id);
}
default: {
throw Error('Unknown action: ' + action.type);
}
}
}
let nextId = 3;
const initialTasks = [
{id: 0, text: 'Visit Kafka Museum', done: true},
{id: 1, text: 'Watch a puppet show', done: false},
{id: 2, text: 'Lennon Wall pic', done: false},
];
Component logic right now can be easier to read when you separate concerns like this. For more detail, see here
Context Hooks
Context lets a component receive information fro distant parents without passing it as props.
About the tutorial of Context Hooks please refer to React hooks In this topic we just care about Context Hooks performance/
A common way to optimize re-rendering performance is to skip unnecessary work. For example, you can tell React to reuse a cached caclculation or to skip a rerender by using:
useMemo
: lets you cache the resultuseCallback
: lets you cache a function definition before passing it down to an optimized componentfunction TodoList({ todos, tab, theme }) { const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]); // ... }
Sometimes, you can’t skip re-rendering because the screen actually needs to update. To prioritize rendering, use one of these hooks:
useTransition
lets you mark a state transition as non-blocking and allow other updates to to interrupt it.useDeferredValue
lets you defer updating a non-critical part of the UI and let other parts update first
useTransition
:
function TabContainer() {
const [isPending, startTransition] = useTransition();
const [tab, setTab] = useState('about');
function selectTab(nextTab) {
startTransition(() => {
setTab(nextTab);
});
}
// ...
}
With a transition your UI stays responsive in the middle of a re-render. If user click to change page, but after that they change their mind and click another tab, they can do without waiting for first rendering finish.
**Building a Suspense-enabled router
function Router() {
const [page, setPage] = useState('/');
const [isPending, startTransition] = useTransition();
function navigate(url) {
startTransition(() => {
setPage(url);
});
}
// ...
Another hook is useCallback
we need to find out what it does?
import { useCallback } from 'react';
export default function ProductPage({ productId, referrer, theme }) {
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}, [productId, referrer]);
Parameter
fn
: the function value that you want to cachedependencies
: the list of all reactive values referenced inside of thefn
code.
Returns On the initial render, useCallback
returns the fn
function you have passed. During subsequent renders, it will either return an already stored fn
function from the last render (if the dependencies haven’t changed), or return the fn
function you have passed during this render.
** Usage **
Skiping re-rendering of components
When you optimize rendering perfromance, you will sometimes need to cache the functions that you pass to child components. Let’s first look at the syntax for how to dothis, and then see in which cases it’s usefull.
function ProductPage({ productId, referrer, theme }) {
// Every time the theme changes, this will be a different function...
function handleSubmit(orderDetails) {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}
return (
<div className={theme}>
{/* ... so ShippingForm's props will never be the same, and it will re-render every time */}
<ShippingForm onSubmit={handleSubmit} />
</div>
);
}
In javascript, a function () {}
or () => {}
always creates a different function, similar to how the {} object literal always create new object. Normally, this wouldn’t be a proble, but it means that ShippingForm
props will never be the same, and your memo
optimization won’t work. This is where useCallback
comes in handy.
function ProductPage({ productId, referrer, theme }) {
// Tell React to cache your function between re-renders...
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}, [productId, referrer]); // ...so as long as these dependencies don't change...
return (
<div className={theme}>
{/* ...ShippingForm will receive the same props and can skip re-rendering */}
<ShippingForm onSubmit={handleSubmit} />
</div>
);
}
This is all about part 1, waiting for part 2.