How to build a to-do list with Next.js

Next.js is a popular React framework that enables efficient server-side rendering and seamless client-side navigation. This makes it easier to build high-performance and SEO-friendly web applications.

In this answer, we will build a basic to-do list in Next.js. Here's a step-by-step guide for this:

Steps to set up a new Next.js project

Follow these steps to set up a new Next.js project:

Step 1: Install Node.js

Next.js is a framework built on top of Node.js, so it is required to have Node.js installed on the machine.

Step 2: Make a directory

Create a new directory for the Next.js project. Do this by opening the terminal or command prompt and running the following command:

mkdir next-application
Next.js project directory creation

Step 3: Navigate to the directory

Navigate into the newly created directory using the following command:

cd next-application
Navigating into the created directory

Step 4: Create a new Next.js project

Then, run the following command in the command prompt or terminal to create a new Next.js project:

npx create-next-app todo-app
Creating a new Next.js project

Now we can run the project by simply using the npm run dev command.

Steps to create a to-do list

Here are the steps to create a basic to-do list:

Step 1: Import the required dependencies and initialize state

import { useState } from 'react';
export default function Home() {
const [task, setTask] = useState('');
const [tasksArray, setTasksArray] = useState([]);
// ...
}

Step 2: Handle input changes

export default function Home() {
// ...
const inputChange = (e) => {
setTask(e.target.value);
};
// ...
}

Step 3: Handle form submission

export default function Home() {
// ...
const inputSubmit = (e) => {
e.preventDefault();
if (task.trim()) {
setTasksArray([...tasksArray, task]);
setTask('');
}
};
// ...
}

Step 4: Handle task deletion

export default function Home() {
// ...
const handleDelete = (index) => {
setTasksArray(tasksArray.filter((_, i) => i !== index));
};
// ...
}

Step 5: Render the UI

export default function Home() {
// ...
return (
<div>
<h1> To-do List in Next.js </h1>
<form onSubmit={inputSubmit}>
<input type="text" value={task} onChange={inputChange} placeholder="Enter a task" />
<button type="submit">Add task</button>
</form>
<ul>
{tasksArray.map((task, index) => (
<li key={index}>
{task}
<button onClick={() => handleDelete(index)}>Delete</button>
</li>
))}
</ul>
</div>
);
}

Code implementation

Click the "Run" button to execute the to-do list coding example.

import { useState } from 'react';

export default function Home() {
  const [task, setTask] = useState('');
  const [tasksArray, setTasksArray] = useState([]);

  const inputChange = (e) => {
    setTask(e.target.value);
  };

  const inputSubmit = (e) => {
    e.preventDefault();
    if (task.trim()) {
      setTasksArray([...tasksArray, task]);
      setTask('');
    }
  };

  const handleDelete = (index) => {
    setTasksArray(tasksArray.filter((_, i) => i !== index));
  };

  return (
    <div>
      <h1> To-do List in Next.js </h1>
      <form onSubmit={inputSubmit}>
        <input type="text" value={task} onChange={inputChange} placeholder="Enter a task" />
        <button type="submit">Add task</button>
      </form>
      <ul>
        {tasksArray.map((task, index) => (
          <li key={index}>
            {task}
            <button onClick={() => handleDelete(index)}>Delete</button>
          </li>
        ))}
      </ul>
    </div>
  );
}
Code Implementation for to-do list

Explanation

The code above can be divided into the following parts:

  • Lines 1–5: In this part, we import the useState hook from React and initialize two state variables: task to track the current task being entered, and tasksArray to store the list of tasks.

  • Lines 7–9: Then, we define the inputChange function that will be called whenever the input field value changes. It updates the task state with the current value of the input field.

  • Lines 11–17: In this section, we define the inputSubmit function, which is used to handle form submissions. The behavior of the default form submission is prevented, and if the task is not empty, it is checked to see if it is added to the tasksArray state array. In addition, an empty string is used to reset the task state.

  • Lines 19–21: In this section, we define the handleDelete method, which deletes a task based on its index from the tasksArray state array. It makes a new array using the filter function that doesn't include the job at the given index.

  • Lines 25–29: The user interface is rendered by this last section. There is a header, an input area, and a submit button on the form. The inputChange function changes the input field's value, which is connected to the task state.

  • Lines 30–37: A list of tasks is generated by mapping the tasksArray state array, with each task presented as a list item. There is a "Delete" button next to each task, which runs the handleDelete method with the task's index.

State management using Context API with Next.js

Another method to build a to-do list is by using context API that integrates the context-based state management into a Next.js application:

Step 1: Create context and provider

// context/TodoContext.js

import React, { createContext, useContext, useReducer } from 'react';

// Create a context for the to-do list state
const TodoContext = createContext();

// Define initial state and reducer function
const initialState = {
  todos: [],
};

const todoReducer = (state, action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return {
        ...state,
        todos: [...state.todos, action.payload],
      };
    case 'DELETE_TODO':
      return {
        ...state,
        todos: state.todos.filter(todo => todo.id !== action.payload),
      };
    default:
      return state;
  }
};

// Define a provider component
export const TodoProvider = ({ children }) => {
  const [state, dispatch] = useReducer(todoReducer, initialState);
  
  return (
    <TodoContext.Provider value={{ state, dispatch }}>
      {children}
    </TodoContext.Provider>
  );
};

// Custom hook to consume the context
export const useTodoContext = () => useContext(TodoContext);
context and app provider

Step 2: Wrap the App with the provider

In this step, we're wrapping our Next.js app with the TodoProvider to make the to-do list state available to all components.

import { TodoProvider } from './context/todo';

function MyApp({ Component, pageProps }) {
  return (
    <TodoProvider>
      <Component {...pageProps} />
    </TodoProvider>
  );
}

export default MyApp;
Wrap the application

Step 3: Use context in the components

In this step, we're using the useTodoContext hook to access the to-do list state and dispatch actions to update it.

// pages/index.js

import { useState } from 'react';
import { useTodoContext } from './context/todo';

export default function Home() {
  const [task, setTask] = useState('');
  const { state, dispatch } = useTodoContext();

  const inputChange = (e) => {
    setTask(e.target.value);
  };

  const inputSubmit = (e) => {
    e.preventDefault();
    if (task.trim()) {
      const newTodo = {
        id: Date.now(),
        text: task,
        completed: false,
      };
      dispatch({ type: 'ADD_TODO', payload: newTodo });
      setTask('');
    }
  };

  const handleDelete = (id) => {
    dispatch({ type: 'DELETE_TODO', payload: id });
  };

  return (
    <div>
      <h1> To-do List in Next.js </h1>
      <form onSubmit={inputSubmit}>
        <input type="text" value={task} onChange={inputChange} placeholder="Enter a task" />
        <button type="submit">Add task</button>
      </form>
      <ul>
        {state.todos.map(todo => (
          <li key={todo.id}>
            {todo.text}
            <button onClick={() => handleDelete(todo.id)}>Delete</button>
          </li>
        ))}
      </ul>
    </div>
  );
}
Use context

Components can access the state and dispatch actions to update it, resulting in a simpler and more maintainable state management solution for our Next.js application.

See the complete executable application below:

// pages/_app.js

import { TodoProvider } from './context/todo.js';

function MyApp({ Component, pageProps }) {
  return (
    <TodoProvider>
      <Component {...pageProps} />
    </TodoProvider>
  );
}

export default MyApp;
Code Implementation using Context APi

Note: The basic code is similar to the application provided above.

Copyright ©2024 Educative, Inc. All rights reserved