Skip to content

How to show the unfinished to-do count in the page title

This example demonstrates how to show the unfinished to-do count in the page title using the useTitle hook. It includes code in both TypeScript and JavaScript versions.

TypeScript version

Watch the live preview.

tsx
import { useEffect, useState } from "react";
import { useTitle } from "./useTitle";

interface Task {
  id: number;
  title: string;
  isDone: boolean;
}

const appTasks: Task[] = [
  { id: 1, title: "Task 1", isDone: false },
  { id: 2, title: "Task 2", isDone: false },
  { id: 3, title: "Task 3", isDone: false },
  { id: 4, title: "Task 4", isDone: false },
  { id: 5, title: "Task 5", isDone: false },
];

export default function App() {
  const [tasks, setTasks] = useState<Task[]>(appTasks);
  const { changeTitle } = useTitle();

  useEffect(() => {
    const unfinishedTasks = tasks.filter((task) => !task.isDone).length;
    changeTitle(`${unfinishedTasks} pending tasks`);
  }, []);

  // Handle check/uncheck task
  const checkTask = (id: number, isDone: boolean) => {
    const updatedTasks = tasks.map((task) =>
      task.id === id ? { ...task, isDone } : task
    );
    setTasks(updatedTasks);

    // Set the title depending on the number of tasks left
    const unfinishedTasks = updatedTasks.filter((task) => !task.isDone).length;
    const newTitle =
      unfinishedTasks !== 1
        ? `${unfinishedTasks} pending tasks`
        : `${unfinishedTasks} pending task`;
    changeTitle(newTitle);
  };

  return (
    <>
      <h1>Tasks</h1>
      <ul>
        {tasks.map((task) => (
          <li key={task.id}>
            <input
              type="checkbox"
              checked={task.isDone}
              onChange={(e) => checkTask(task.id, e.target.checked)}
            />
            {task.title}
          </li>
        ))}
      </ul>
    </>
  );
}
typescript
import { useState } from "react";

export const useTitle = () => {
  const [title, setTitle] = useState<string>(document.title);

  const changeTitle = (newTitle: string) => {
    document.title = newTitle;
    setTitle(newTitle);
  };

  return { title, changeTitle };
};

JavaScript version

Watch the live preview.

jsx
import { useEffect, useState } from "react";
import { useTitle } from "./useTitle";

const appTasks = [
  { id: 1, title: "Task 1", isDone: false },
  { id: 2, title: "Task 2", isDone: false },
  { id: 3, title: "Task 3", isDone: false },
  { id: 4, title: "Task 4", isDone: false },
  { id: 5, title: "Task 5", isDone: false },
];

export default function App() {
  const [tasks, setTasks] = useState(appTasks);
  const { changeTitle } = useTitle();

  useEffect(() => {
    const unfinishedTasks = tasks.filter((task) => !task.isDone).length;
    changeTitle(`${unfinishedTasks} pending tasks`);
  }, []);

  // Handle check/uncheck task
  const checkTask = (id, isDone) => {
    const updatedTasks = tasks.map((task) =>
      task.id === id ? { ...task, isDone } : task
    );
    setTasks(updatedTasks);

    // Set the title depending on the number of tasks left
    const unfinishedTasks = updatedTasks.filter((task) => !task.isDone).length;
    const newTitle =
      unfinishedTasks !== 1
        ? `${unfinishedTasks} pending tasks`
        : `${unfinishedTasks} pending task`;
    changeTitle(newTitle);
  };

  return (
    <>
      <h1>Tasks</h1>
      <ul>
        {tasks.map((task) => (
          <li key={task.id}>
            <input
              type="checkbox"
              checked={task.isDone}
              onChange={(e) => checkTask(task.id, e.target.checked)}
            />
            {task.title}
          </li>
        ))}
      </ul>
    </>
  );
}
javascript
import { useState } from "react";

export const useTitle = () => {
  const [title, setTitle] = useState(document.title);

  const changeTitle = (newTitle) => {
    document.title = newTitle;
    setTitle(newTitle);
  };

  return { title, changeTitle };
};

Released under the MIT License.