[React] Preventing extra re-rendering with function component by using React.memo and useCallback

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.