Got the idea form this lesson. Not sure whether it is the ncessary, no othere better way to handle it.
Have a TodoList component, every time types in NewTodo input fields, will trigger the re-rendering for all components.
import React, { useState, useContext, useCallback } from "react"; import NewTodo from "./NewTodo"; import TodoItem from "./TodoItem5"; import { Container, List } from "./Styled"; import About from "./About"; import { useTodosWithLocalStorage, useKeyDown } from "./hooks"; import { useTitle as useDocumentTitle } from "react-use"; import ThemeContext from "./ThemeContext"; const incompleteTodoCount = todos => todos.reduce((memo, todo) => (!todo.completed ? memo + 1 : memo), 0); export default function TodoList() { const [newTodo, updateNewTodo] = useState(""); const [todos, dispatch] = useTodosWithLocalStorage([]); const inCompleteCount = incompleteTodoCount(todos); const title = inCompleteCount ? `Todos (${inCompleteCount})` : "Todos"; useDocumentTitle(title); let [showAbout, setShowAbout] = useKeyDown( { "?": true, Escape: false }, false ); const handleNewSubmit = e => { e.preventDefault(); dispatch({ type: "ADD_TODO", text: newTodo }); updateNewTodo(""); }; const theme = useContext(ThemeContext); return ( <Container todos={todos}> <NewTodo onSubmit={handleNewSubmit} value={newTodo} onChange={e => updateNewTodo(e.target.value)} /> {!!todos.length && ( <List theme={theme}> {todos.map(todo => ( <TodoItem key={todo.id} todo={todo} onChange={id => dispatch({ type: "TOGGLE_TODO", id })} onDelete={id => dispatch({ type: "DELETE_TODO", id })} /> ))} </List> )} <About isOpen={showAbout} onClose={() => setShowAbout(false)} /> </Container> ); }
The way to solve the problem is
1. For every callback:
<TodoItem onChange={id => dispatch({ type: "TOGGLE_TODO", id })} onDelete={id => dispatch({ type: "DELETE_TODO", id })} /> <About isOpen={showAbout} onClose={() => setShowAbout(false)} />
We should replace with useCallback hooks:
const handleChange = useCallback( id => dispatch({ type: "TOGGLE_TODO", id }), [] ); const handleDelete = useCallback( id => dispatch({ type: "DELETE_TODO", id }), [] ); const handleClose = userCallback( () => setShowAbout(false), [] )
{!!todos.length && ( <List theme={theme}> {todos.map(todo => ( <TodoItem key={todo.id} todo={todo} onChange={handleChange} onDelete={handleDelete} /> ))} </List> )} <About isOpen={showAbout} onClose={handleClose} />
This helps to reduce some extra re-rendering.
2. Using Reac.memo for function component:
import React, { useContext } from "react"; import Checkbox from "./Checkbox"; import ThemeContext from "./ThemeContext"; import { Button, Item } from "./Styled"; const TodoItem = React.memo(({ todo, onChange, onDelete }) => { console.log("TodoItem5", { todo, onChange, onDelete }); const theme = useContext(ThemeContext); return ( <Item key={todo.id} theme={theme}> <Checkbox id={todo.id} label={todo.text} checked={todo.completed} onChange={onChange.bind(this, todo.id)} /> <Button onClick={onDelete.bind(this, todo.id)} theme={theme}> x </Button> </Item> ); }); export default TodoItem;
import React from "react"; import styled from "react-emotion"; import { Dialog } from "@reach/dialog"; ... export default React.memo(function TodoItem({ isOpen, onClose }) { return ( <Dialog isOpen={isOpen}> ... </Dialog> ); });
Assume that every times I should wrap function component with React.memo() and use useCallback
whenever necessary.