Skip to main content

Making a TODO app with React

Once in a time a decent dev must renew his knowledge of tech. So I decided to recollect react by creating a basic TODO app with it.

To start it one need to install react project. I prefer `pnpm` and `nvm` for managing packages and node versions respectively. Here are my steps to install React:

Step 1: Install pnpm


If you don’t already have pnpm installed, install it globally via npm or corepack:

npm install -g pnpm


Or, if you’re using Node.js >=16.13, you can enable it with:

corepack enable


Step 2: Create a Project Directory


Create a new directory for your React app and navigate into it:

mkdir react-todo
cd react-todo


Step 3: Initialize the Project


Initialize the project with pnpm:

pnpm init


Follow the prompts or press Enter to accept the defaults.


Step 4: Install React and ReactDOM


Install React and ReactDOM libraries, along with vite for an optimized development experience:

pnpm add react react-dom
pnpm add -D vite


Step 5: Set Up Vite


Initialize a basic vite project setup:

pnpm exec vite


Follow the prompts, choosing react as the framework and any additional options as desired.


Step 6: Update package.json Scripts


Edit your package.json file to include a start script for Vite:

"scripts": {
  "dev": "vite",
  "build": "vite build",
  "serve": "vite preview"
}

Step 7: Create the App Structure


Make a basic React app structure:

1. Create src directory:

mkdir src





2. Create index.html:

In the root of your project, add the following contents:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Hello World</title>
</head>
<body>
  <div id="root"></div>
  <script type="module" src="/src/main.jsx"></script>
</body>
</html>

    3. Create src/main.jsx:

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);


4. Create src/App.jsx:

import React from 'react';

const App = () => {
  return <h1>TODO:</h1>;
};

export default App;


Step 8: Start the Development Server


Run the development server to preview your app:

pnpm dev

Open your browser and navigate to the URL displayed in the terminal (usually http://localhost:5173 ).


Step 9: Confirm the Project Structure


Ensure your project structure looks like this:

├── package.json
├── pnpm-lock.yaml
├── node_modules/
├── vite.config.js (optional, for custom configurations)
├── index.html
└── src/
    ├── App.jsx
    ├── main.jsx


Step 10: Solve issues that may occur


Mine was having `ERR_PNPM_RECURSIVE_EXEC_FIRST_FAIL  Command "dev" not found ` error. I have solved it by manually running vite for now:


user@ro react-todo % pnpm dev
 ERR_PNPM_RECURSIVE_EXEC_FIRST_FAIL  Command "dev" not found
user@ro react-todo % pnpm list
Legend: production dependency, optional only, dev only

react-todo@1.0.0 /Users/user/dev/me/react-todo

dependencies:
react 19.0.0
react-dom 19.0.0

devDependencies:
vite 6.0.5
user@ro react-todo % pnpm exec vite
Port 5173 is in use, trying another one...

  VITE v6.0.5  ready in 84 ms

  ➜  Local:   http://localhost:5174/Network: use --host to expose
  ➜  press h + enter to show help

That did the trick and I was up and runnging for farther manipulations.


Here’s how to extend the “Hello World” app into a basic TODO app with the following features:

• Add tasks.

• Mark tasks as complete.

• Store tasks on a server.


We’ll achieve this in a step-by-step manner.



Step 11: Update the App to a TODO Component


Replace the src/App.jsx content with a basic TODO app.

import React, { useState, useEffect } from 'react';

const App = () => {
  const [tasks, setTasks] = useState([]);
  const [newTask, setNewTask] = useState('');

  // Fetch tasks from the server
  useEffect(() => {
    fetch('http://localhost:3000/tasks')
      .then((response) => response.json())
      .then((data) => setTasks(data))
      .catch((error) => console.error('Error fetching tasks:', error));
  }, []);

  // Add a new task
  const addTask = () => {
    if (!newTask.trim()) return;

    const task = { text: newTask, completed: false };
    fetch('http://localhost:3000/tasks', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(task),
    })
      .then((response) => response.json())
      .then((savedTask) => {
        setTasks([...tasks, savedTask]);
        setNewTask('');
      })
      .catch((error) => console.error('Error adding task:', error));
  };

  // Toggle task completion
  const toggleTask = (taskId) => {
    const taskToUpdate = tasks.find((task) => task.id === taskId);
    if (!taskToUpdate) return;

    fetch(`http://localhost:3000/tasks/${taskId}`, {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ ...taskToUpdate, completed: !taskToUpdate.completed }),
    })
      .then((response) => response.json())
      .then((updatedTask) => {
        setTasks(tasks.map((task) => (task.id === updatedTask.id ? updatedTask : task)));
      })
      .catch((error) => console.error('Error updating task:', error));
  };

  return (
    <div style={{ padding: '20px', maxWidth: '400px', margin: 'auto', fontFamily: 'Arial, sans-serif' }}>
      <h1>TODO App</h1>
      <div style={{ display: 'flex', gap: '10px', marginBottom: '20px' }}>
        <input
          type="text"
          value={newTask}
          onChange={(e) => setNewTask(e.target.value)}
          placeholder="Enter a task..."
          style={{ flex: 1, padding: '8px' }}
        />
        <button onClick={addTask} style={{ padding: '8px 12px' }}>
          Add
        </button>
      </div>
      <ul style={{ listStyle: 'none', padding: 0 }}>
        {tasks.map((task) => (
          <li
            key={task.id}
            style={{
              display: 'flex',
              justifyContent: 'space-between',
              padding: '8px 0',
              textDecoration: task.completed ? 'line-through' : 'none',
            }}
          >
            <span onClick={() => toggleTask(task.id)} style={{ cursor: 'pointer' }}>
              {task.text}
            </span>
          </li>
        ))}
      </ul>
    </div>
  );
};

export default App;


Step 12: Set Up the Server


We’ll use a simple JSON server to handle tasks.

1. Install JSON Server globally if not already installed:

 pnpm add -g json-server

2. Create a db.json file at the root of your project with initial data:


{
  "tasks": [
    { "id": 1, "text": "Sample Task 1", "completed": false },
    { "id": 2, "text": "Sample Task 2", "completed": true }
  ]
}

3. Start the server:

json-server --watch todo.json --port 3000

The server will be running at http://localhost:3000.


Step 13: Test the App


1. Run the React app:

pnpm dev

2. Visit the app in your browser (e.g., http://localhost:5173).

3. Add tasks, toggle their completion, and see the data persist on the server.

Step 14: Add Styles (Optional)


You can improve the app’s look by adding styles in a separate src/styles.css file and importing it into src/main.jsx:

import './styles.css';

For example, a styles.css file might contain:

body {
  font-family: Arial, sans-serif;
  background: #f9f9f9;
  margin: 0;
  padding: 0;
}

h1 {
  color: #333;
}

button {
  background-color: #007bff;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

button:hover {
  background-color: #0056b3;
}
You now have a functional TODO app with server-side persistence. 

Let’s style the TODO items to include checkboxes for marking tasks as complete. This will provide a better user experience with a clean, modern look.


Step 1: Update the App.jsx


Modify the TODO list in App.jsx to include checkboxes for marking tasks as complete. Here’s the updated code:

import React, { useState, useEffect } from 'react';

const App = () => {
  const [tasks, setTasks] = useState([]);
  const [newTask, setNewTask] = useState('');

  // Fetch tasks from the server
  useEffect(() => {
    fetch('http://localhost:3000/tasks')
      .then((response) => response.json())
      .then((data) => setTasks(data))
      .catch((error) => console.error('Error fetching tasks:', error));
  }, []);

  // Add a new task
  const addTask = () => {
    if (!newTask.trim()) return;

    const task = { text: newTask, completed: false };
    fetch('http://localhost:3000/tasks', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(task),
    })
      .then((response) => response.json())
      .then((savedTask) => {
        setTasks([...tasks, savedTask]);
        setNewTask('');
      })
      .catch((error) => console.error('Error adding task:', error));
  };

  // Toggle task completion
  const toggleTask = (taskId) => {
    const taskToUpdate = tasks.find((task) => task.id === taskId);
    if (!taskToUpdate) return;

    fetch(`http://localhost:3000/tasks/${taskId}`, {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ ...taskToUpdate, completed: !taskToUpdate.completed }),
    })
      .then((response) => response.json())
      .then((updatedTask) => {
        setTasks(tasks.map((task) => (task.id === updatedTask.id ? updatedTask : task)));
      })
      .catch((error) => console.error('Error updating task:', error));
  };

  return (
    <div style={{ padding: '20px', maxWidth: '400px', margin: 'auto', fontFamily: 'Arial, sans-serif' }}>
      <h1>TODO App</h1>
      <div style={{ display: 'flex', gap: '10px', marginBottom: '20px' }}>
        <input
          type="text"
          value={newTask}
          onChange={(e) => setNewTask(e.target.value)}
          placeholder="Enter a task..."
          style={{ flex: 1, padding: '8px' }}
        />
        <button onClick={addTask} style={{ padding: '8px 12px' }}>
          Add
        </button>
      </div>
      <ul style={{ listStyle: 'none', padding: 0 }}>
        {tasks.map((task) => (
          <li
            key={task.id}
            style={{
              display: 'flex',
              alignItems: 'center',
              justifyContent: 'space-between',
              padding: '8px 0',
              borderBottom: '1px solid #ddd',
            }}
          >
            <label style={{ display: 'flex', alignItems: 'center', cursor: 'pointer', gap: '10px' }}>
              <input
                type="checkbox"
                checked={task.completed}
                onChange={() => toggleTask(task.id)}
              />
              <span
                style={{
                  textDecoration: task.completed ? 'line-through' : 'none',
                  color: task.completed ? '#999' : '#000',
                }}
              >
                {task.text}
              </span>
            </label>
          </li>
        ))}
      </ul>
    </div>
  );
};

export default App;


Step 2: Style the App


To enhance the design, you can add a styles.css file. Here’s a simple example:


src/styles.css:

body {
  font-family: Arial, sans-serif;
  background: #f9f9f9;
  margin: 0;
  padding: 0;
}

h1 {
  text-align: center;
  color: #333;
}

button {
  background-color: #007bff;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 16px;
}

button:hover {
  background-color: #0056b3;
}

input[type="text"] {
  border: 1px solid #ddd;
  border-radius: 4px;
  font-size: 16px;
}

input[type="checkbox"] {
  width: 18px;
  height: 18px;
  cursor: pointer;
}

ul {
  padding: 0;
  margin: 0;
}

li {
  transition: background-color 0.3s ease;
}

li:hover {
  background-color: #f0f0f0;
}


Result


Each TODO item now has a checkbox.

Checked tasks have their text styled with a strikethrough and muted color.

The overall app has a clean, minimal design.

Let’s break the App.jsx into separate components for better readability and maintainability. We’ll create three components:

1. TaskInput: For adding new tasks.

2. TaskItem: For rendering individual task items.

3. TaskList: For rendering the list of tasks.


Updated App.jsx


Here’s the updated App.jsx with the new structure:

import React, { useState, useEffect } from 'react';
import TaskInput from './TaskInput';
import TaskList from './TaskList';

const App = () => {
  const [tasks, setTasks] = useState([]);

  // Fetch tasks from the server
  useEffect(() => {
    fetch('http://localhost:3000/tasks')
      .then((response) => response.json())
      .then((data) => setTasks(data))
      .catch((error) => console.error('Error fetching tasks:', error));
  }, []);

  // Add a new task
  const addTask = (text) => {
    const task = { text, completed: false };
    fetch('http://localhost:3000/tasks', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(task),
    })
      .then((response) => response.json())
      .then((savedTask) => setTasks([...tasks, savedTask]))
      .catch((error) => console.error('Error adding task:', error));
  };

  // Toggle task completion
  const toggleTask = (taskId) => {
    const taskToUpdate = tasks.find((task) => task.id === taskId);
    if (!taskToUpdate) return;

    fetch(`http://localhost:3000/tasks/${taskId}`, {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ ...taskToUpdate, completed: !taskToUpdate.completed }),
    })
      .then((response) => response.json())
      .then((updatedTask) => {
        setTasks(tasks.map((task) => (task.id === updatedTask.id ? updatedTask : task)));
      })
      .catch((error) => console.error('Error updating task:', error));
  };

  return (
    <div style={{ padding: '20px', maxWidth: '400px', margin: 'auto', fontFamily: 'Arial, sans-serif' }}>
      <h1>TODO App</h1>
      <TaskInput onAddTask={addTask} />
      <TaskList tasks={tasks} onToggleTask={toggleTask} />
    </div>
  );
};

export default App;


TaskInput.jsx


This component handles adding new tasks.

import React, { useState } from 'react';

const TaskInput = ({ onAddTask }) => {
  const [newTask, setNewTask] = useState('');

  const handleAddTask = () => {
    if (!newTask.trim()) return;
    onAddTask(newTask);
    setNewTask('');
  };

  return (
    <div style={{ display: 'flex', gap: '10px', marginBottom: '20px' }}>
      <input
        type="text"
        value={newTask}
        onChange={(e) => setNewTask(e.target.value)}
        placeholder="Enter a task..."
        style={{ flex: 1, padding: '8px' }}
      />
      <button onClick={handleAddTask} style={{ padding: '8px 12px' }}>
        Add
      </button>
    </div>
  );
};

export default TaskInput;


TaskItem.jsx


This component represents a single task with a checkbox.

import React from 'react';

const TaskItem = ({ task, onToggleTask }) => {
  return (
    <li
      style={{
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'space-between',
        padding: '8px 0',
        borderBottom: '1px solid #ddd',
      }}
    >
      <label style={{ display: 'flex', alignItems: 'center', cursor: 'pointer', gap: '10px' }}>
        <input
          type="checkbox"
          checked={task.completed}
          onChange={() => onToggleTask(task.id)}
        />
        <span
          style={{
            textDecoration: task.completed ? 'line-through' : 'none',
            color: task.completed ? '#999' : '#000',
          }}
        >
          {task.text}
        </span>
      </label>
    </li>
  );
};

export default TaskItem;


TaskList.jsx


This component renders the list of tasks using the TaskItem component.

import React from 'react';
import TaskItem from './TaskItem';

const TaskList = ({ tasks, onToggleTask }) => {
  return (
    <ul style={{ listStyle: 'none', padding: 0 }}>
      {tasks.map((task) => (
        <TaskItem key={task.id} task={task} onToggleTask={onToggleTask} />
      ))}
    </ul>
  );
};

export default TaskList;


Final Project Structure


Your project structure should now look like this:

src/
├── App.jsx
├── TaskInput.jsx
├── TaskItem.jsx
├── TaskList.jsx
├── main.jsx
└── styles.css


Benefits of Refactoring🚀


Readability: Smaller components are easier to understand.

Reusability: Components like TaskItem can be reused in other parts of the app if needed.

Scalability: Adding new features or modifying existing ones is simpler when components are separated.


Lets modify it farther to make App.jsx even smaller. What can we do?


To make App.jsx even smaller and more focused, we can extract state management and business logic into custom hooks or context providers. Here’s how to achieve that:


Step 1: Create a Custom Hook for Task Management


Move the logic for fetching tasks, adding tasks, and toggling task completion into a custom hook called useTasks.


src/hooks/useTasks.js:

import { useState, useEffect } from 'react';

const useTasks = (apiUrl) => {
  const [tasks, setTasks] = useState([]);

  useEffect(() => {
    fetch(`${apiUrl}/tasks`)
      .then((response) => response.json())
      .then((data) => setTasks(data))
      .catch((error) => console.error('Error fetching tasks:', error));
  }, [apiUrl]);

  const addTask = (text) => {
    const task = { text, completed: false };
    fetch(`${apiUrl}/tasks`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(task),
    })
      .then((response) => response.json())
      .then((savedTask) => setTasks([...tasks, savedTask]))
      .catch((error) => console.error('Error adding task:', error));
  };

  const toggleTask = (taskId) => {
    const taskToUpdate = tasks.find((task) => task.id === taskId);
    if (!taskToUpdate) return;

    fetch(`${apiUrl}/tasks/${taskId}`, {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ ...taskToUpdate, completed: !taskToUpdate.completed }),
    })
      .then((response) => response.json())
      .then((updatedTask) => {
        setTasks(tasks.map((task) => (task.id === updatedTask.id ? updatedTask : task)));
      })
      .catch((error) => console.error('Error updating task:', error));
  };

  return { tasks, addTask, toggleTask };
};

export default useTasks;


Step 2: Update App.jsx to Use the Custom Hook


With the logic moved to the custom hook, App.jsx is now much simpler:


src/App.jsx:

import React from 'react';
import useTasks from './hooks/useTasks';
import TaskInput from './TaskInput';
import TaskList from './TaskList';

const App = () => {
  const apiUrl = 'http://localhost:3000'; // API URL
  const { tasks, addTask, toggleTask } = useTasks(apiUrl);

  return (
    <div style={{ padding: '20px', maxWidth: '400px', margin: 'auto', fontFamily: 'Arial, sans-serif' }}>
      <h1>TODO App</h1>
      <TaskInput onAddTask={addTask} />
      <TaskList tasks={tasks} onToggleTask={toggleTask} />
    </div>
  );
};

export default App;


Step 3: Refactor the API URL to a Context (Optional)


If you plan to reuse the API URL or centralize configuration, you can create an ApiProvider using React Context.


src/context/ApiProvider.jsx:

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

const ApiContext = createContext();

export const ApiProvider = ({ children, apiUrl }) => (
  <ApiContext.Provider value={apiUrl}>{children}</ApiContext.Provider>
);

export const useApi = () => useContext(ApiContext);


Step 4: Update the App to Use the API Context


Wrap the app in the ApiProvider and refactor the useTasks hook to use the API URL from context:


Update src/main.jsx:

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { ApiProvider } from './context/ApiProvider';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <ApiProvider apiUrl="http://localhost:3000">
    <App />
  </ApiProvider>
);


Update src/hooks/useTasks.js:

import { useState, useEffect } from 'react';
import { useApi } from '../context/ApiProvider';

const useTasks = () => {
  const apiUrl = useApi();
  const [tasks, setTasks] = useState([]);

  useEffect(() => {
    fetch(`${apiUrl}/tasks`)
      .then((response) => response.json())
      .then((data) => setTasks(data))
      .catch((error) => console.error('Error fetching tasks:', error));
  }, [apiUrl]);

  const addTask = (text) => {
    const task = { text, completed: false };
    fetch(`${apiUrl}/tasks`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(task),
    })
      .then((response) => response.json())
      .then((savedTask) => setTasks([...tasks, savedTask]))
      .catch((error) => console.error('Error adding task:', error));
  };

  const toggleTask = (taskId) => {
    const taskToUpdate = tasks.find((task) => task.id === taskId);
    if (!taskToUpdate) return;

    fetch(`${apiUrl}/tasks/${taskId}`, {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ ...taskToUpdate, completed: !taskToUpdate.completed }),
    })
      .then((response) => response.json())
      .then((updatedTask) => {
        setTasks(tasks.map((task) => (task.id === updatedTask.id ? updatedTask : task)));
      })
      .catch((error) => console.error('Error updating task:', error));
  };

  return { tasks, addTask, toggleTask };
};

export default useTasks;


Resulting Project Structure


The project is now modular and clean:

src/
├── App.jsx
├── context/
│   └── ApiProvider.jsx
├── hooks/
│   └── useTasks.js
├── TaskInput.jsx
├── TaskItem.jsx
├── TaskList.jsx
├── main.jsx
└── styles.css


Benefits of Further Refactoring


1. Smaller App.jsx: Focuses purely on rendering.

2. Reusability: useTasks can be reused in other components or applications.

3. Centralized Config: The API URL is managed globally with React Context.

4. Scalability: Adding more hooks or features doesn’t clutter the main app file.

Lets add new component to this menu. That would navigate us through the app...


To add navigation to the app, we’ll incorporate a menu component and use React Router for navigation between pages. Here’s how we can enhance the app:


Step 1: Install React Router


Run the following command to add React Router to your project:

pnpm add react-router-dom

Step 2: Create a Menu Component


The Menu component will act as the navigation bar.


src/Menu.jsx:

import React from 'react';
import { NavLink } from 'react-router-dom';

const Menu = () => {
  const linkStyle = {
    padding: '10px 20px',
    textDecoration: 'none',
    color: '#007bff',
    fontWeight: 'bold',
  };

  const activeStyle = {
    color: '#0056b3',
    textDecoration: 'underline',
  };

  return (
    <nav style={{ marginBottom: '20px', borderBottom: '1px solid #ddd', paddingBottom: '10px' }}>
      <NavLink to="/" style={linkStyle} activeStyle={activeStyle} exact>
        Home
      </NavLink>
      <NavLink to="/tasks" style={linkStyle} activeStyle={activeStyle}>
        Tasks
      </NavLink>
      <NavLink to="/about" style={linkStyle} activeStyle={activeStyle}>
        About
      </NavLink>
    </nav>
  );
};

export default Menu;


Step 3: Update App.jsx for Routing


We’ll use React Router to define routes for the app. Update App.jsx to include navigation and routing logic.


src/App.jsx:

import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Menu from './Menu';
import TaskPage from './pages/TaskPage';
import AboutPage from './pages/AboutPage';

const App = () => {
  return (
    <Router>
      <div style={{ padding: '20px', maxWidth: '600px', margin: 'auto', fontFamily: 'Arial, sans-serif' }}>
        <h1>TODO App</h1>
        <Menu />
        <Routes>
          <Route path="/" element={<HomePage />} />
          <Route path="/tasks" element={<TaskPage />} />
          <Route path="/about" element={<AboutPage />} />
        </Routes>
      </div>
    </Router>
  );
};

const HomePage = () => (
  <div>
    <h2>Welcome to the TODO App</h2>
    <p>Navigate to the "Tasks" page to manage your tasks or learn more about this app on the "About" page.</p>
  </div>
);

export default App;


Step 4: Create Pages for Tasks and About


src/pages/TaskPage.jsx:


This page renders the TODO functionality.

import React from 'react';
import TaskInput from '../TaskInput';
import TaskList from '../TaskList';
import useTasks from '../hooks/useTasks';

const TaskPage = () => {
  const apiUrl = 'http://localhost:3000'; // API URL
  const { tasks, addTask, toggleTask } = useTasks(apiUrl);

  return (
    <div>
      <h2>Task Manager</h2>
      <TaskInput onAddTask={addTask} />
      <TaskList tasks={tasks} onToggleTask={toggleTask} />
    </div>
  );
};

export default TaskPage;


src/pages/AboutPage.jsx:


This page displays information about the app.

import React from 'react';

const AboutPage = () => {
  return (
    <div>
      <h2>About This App</h2>
      <p>
        This is a simple TODO app built with React. It allows you to create tasks, mark them as complete, 
        and store data on a server. Navigation is handled with React Router.
      </p>
    </div>
  );
};

export default AboutPage;


Step 5: Final Project Structure


After these changes, your project structure will look like this:

src/
├── App.jsx
├── Menu.jsx
├── TaskInput.jsx
├── TaskItem.jsx
├── TaskList.jsx
├── context/
│   └── ApiProvider.jsx
├── hooks/
│   └── useTasks.js
├── pages/
│   ├── AboutPage.jsx
│   ├── TaskPage.jsx
├── main.jsx
└── styles.css

Step 6: Result


Home Page: A welcome page with navigation instructions.

Tasks Page: The TODO management functionality.

About Page: Information about the app.


Menu Navigation


The menu lets users switch between the “Home,” “Tasks,” and “About” pages seamlessly.


Here is a final result on my github with some errors corrected that I'm leaving for the reader to handle as a practice task.

https://github.com/garmoncheg/react-todo

Hope it helps someone starting with react.

Comments

Popular posts from this blog

Pretty git Log

SO you dislike git log output in console like me and do not use it... Because it looks like so: How about this one? It's quite easy... Just type: git log - - graph - - pretty = format : '%Cred%h%Creset -%C ( yellow ) %d%Creset %s %Cgreen ( %cr) %C ( bold blue ) <%an>%Creset' - - abbrev - commit - - It may be hard to enter such an easy command every time. Let's make an alias instead... Copypaste this to your terminal: git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit --" And use simple command to see this pretty log instead: git lg Now in case you want to see lines that changed use: git lg - p In order for this command to work remove  the -- from the end of the alias. May the code be with you! NOTE: this article is a rewritten copy of  http://coderwall.com/p/euwpig?i=3&p=1&t=git   and have b...

Django: Resetting Passwords (with internal tools)

I have had a task recently. It was about adding a forms/mechanism for resetting a password in our Django based project. We have had our own registration system ongoing... It's a corporate sector project. So you can not go and register yourself. Admins (probably via LDAP sync) will register your email/login in system. So you have to go there and only set yourself a password. For security reasons you can not register. One word. First I've tried to find standart decision. From reviewed by me were: django-registration and django password-reset . These are nice tools to install and give it a go. But I've needed a more complex decision. And the idea was that own bicycle is always better. So I've thought of django admin and that it has all the things you need to do this yourself in no time. (Actually it's django.contrib.auth part of django, but used out of the box in Admin UI) You can find views you need for this in there. they are: password_reset password_reset_...

Vagrant error: * Unknown configuration section 'hostmanager'.

Sometimes you get a vagrant environment or boilerplate with a Vagrantfile config in there and do a vagrant up command. And see some errors. like this: There are errors in the configuration of this machine . Please fix the following errors and try again : Vagrant: * Unknown configuration section 'hostmanager'. To fix this one needs: $ vagrant plugin install vagrant - hostmanager Installing the ' vagrant-hostmanager ' plugin . This can take a few minutes . . . Fetching : vagrant - hostmanager - 1.8 .6 . gem ( 100 % ) Installed the plugin ' vagrant-hostmanager (1.8.6) ' ! So command to fix this as follows: vagrant plugin install vagrant-hostmanager