Open In App

Build a Habit Tracker App with React and local storage

Last Updated : 29 Feb, 2024
Improve
Improve
Like Article
Like
Save
Share
Report

In this article, we will be Building a Habit Tracker App with React and local storage to persist habit data. The habit tracker app is a web application that helps users track their daily habits and progress over time. It provides a simple and intuitive interface for users to add new habits with descriptions, edit, remove, and mark them as completed each day and the app will display a summary of their progress for each habit.

Preview Output: Let us have a look at how the final output will look like.

Screenshot-(479)

Prerequisites:

Approach to build a Habit Tracker App with React and Local Storage:

  • Habit tracker involves creating UI that enables user to add, edit, remove and track the progress of habits.
  • We will Use React Hooks (useState, useEffect) to manage state for habits. Use localStorage to persist the habits data.
  • We will be splitting the code into components so codebase remains structured.
  • Application will start with adding new habits, once user add new habits they can edit and remove the habits and can see the weekly progress of habit.
  • User can mark the habits as done or undone.

Steps to Create a React App & Install Required Modules:

Step 1: Create a new React JS project using the following command

npx create-react-app habit-tracker

Step 2: Navigate to project directory

cd habit-tracker

Step 3: Install the require packages in your application using the following command.

npm i bootstrap
npm install react-icons --save

Project structure:

Screenshot-(464)

The updated dependencies in package.json will look like this:

"dependencies": {
"bootstrap": "^5.3.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^5.0.1",
"react-router": "^6.22.1",
"react-router-dom": "^6.22.1",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
}

Example: Write the following code in respective files to build Habit Tracker App

Javascript




// App.js
import React, { useState, useEffect } from 'react';
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
import 'bootstrap/dist/css/bootstrap.min.css';
import NavBar from './Components/NavBar';
import AddHabit from './Components/AddHabit';
import ViewWeekly from './Components/ViewWeekly';
 
const App = () => {
 
  return (
    <>
      <NavBar />
      <div className="container mt-5">
        <Routes>
          <Route path="/" element={<AddHabit  />} />
          <Route path="/view-weekly" element={<ViewWeekly  />} />
        </Routes>
      </div>
    </>
  );
};
 
export default App;


Javascript




// NavBar.js
import React from 'react';
import { Link } from 'react-router-dom';
import 'bootstrap/dist/css/bootstrap.min.css';
const Navbar = () => {
    return (
        <nav className="navbar navbar-expand-lg
     navbar-dark bg-success">
            <div className="container">
                <Link className="navbar-brand"
                    to="/">Habit Tracker
                </Link>
                <div className="collapse navbar-collapse">
                    <ul className="navbar-nav mr-auto">
                        <li className="nav-item">
                            <Link className="nav-link"
                                to="/">Add New Habit
                            </Link>
                        </li>
                        <li className="nav-item">
                            <Link className="nav-link"
                                to="/view-weekly">View Weekly
                            </Link>
                        </li>
                    </ul>
                </div>
            </div>
        </nav>
    );
};
 
export default Navbar;


Javascript




// AddHabit.js
 
import React, { useState, useEffect } from 'react';
import { AiOutlineDelete, AiOutlineEdit } from 'react-icons/ai';
import 'bootstrap/dist/css/bootstrap.min.css';
 
const AddHabit = () => {
  // Load initial habits from localStorage or use an empty array if none exist
  const initialHabits = JSON.parse(localStorage.getItem('habits')) || [];
 
  const [habits, setHabits] = useState(initialHabits);
  const [habit, setHabit] = useState('');
  const [description, setDescription] = useState('');
  const [editIndex, setEditIndex] = useState(null);
 
  useEffect(() => {
    // Save habits to localStorage whenever they change
    localStorage.setItem('habits', JSON.stringify(habits));
  }, [habits]);
 
  const handleSubmit = (e) => {
    e.preventDefault();
    if (!habit.trim()) {
      alert('Habit name cannot be empty');
      return;
    }
    if (editIndex !== null) {
      const updatedHabits = [...habits];
      updatedHabits[editIndex].name = habit;
      updatedHabits[editIndex].description = description;
      setHabits(updatedHabits);
      setEditIndex(null);
    } else {
      setHabits([...habits, { name: habit, description, status: { Monday: false, Tuesday: false, Wednesday: false, Thursday: false, Friday: false, Saturday: false, Sunday: false } }]);
    }
    setHabit('');
    setDescription('');
  };
 
  const removeHabit = (index) => {
    const updatedHabits = habits.filter((_, i) => i !== index);
    setHabits(updatedHabits);
  };
 
  const editHabit = (index) => {
    setHabit(habits[index].name);
    setDescription(habits[index].description || ''); // Handle case where description may be undefined
    setEditIndex(index);
  };
 
  return (
    <div>
      <h3 className='text-success'>GeekForGeeks Habit Tracker</h3>
      <h4>Add New Habit</h4>
      <form onSubmit={handleSubmit}>
        <div className="mb-3">
          <input
            type="text"
            className="form-control"
            placeholder="Enter habit name"
            value={habit}
            onChange={(e) => setHabit(e.target.value)}
          />
        </div>
        <div className="mb-3">
          <input
            type="textarea"
            className="form-control"
            placeholder="Enter habit description"
            value={description}
            onChange={(e) => setDescription(e.target.value)}
          />
        </div>
        <button type="submit" className="btn btn-primary">
          {editIndex !== null ? 'Save' : 'Add'}
        </button>
      </form>
      <table className="table">
        <thead>
          <tr>
            <th>Index</th>
            <th>Habit</th>
            <th>Description</th>
            <th>Actions</th>
          </tr>
        </thead>
        <tbody>
          {habits.map((habit, index) => (
            <tr key={index}>
              <td>{index + 1}</td>
              <td>{habit.name}</td>
              <td>{habit.description}</td>
              <td>
                <button className="btn btn-primary me-2" onClick={() => editHabit(index)}>
                  <AiOutlineEdit />
                </button>
                <button className="btn btn-danger" onClick={() => removeHabit(index)}>
                  <AiOutlineDelete />
                </button>
              </td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
};
 
export default AddHabit;


Javascript




// ViewWeekly.js
 
import React, { useState } from 'react';
import { FaCheck, FaTimes } from 'react-icons/fa';
import 'bootstrap/dist/css/bootstrap.min.css';
 
const ViewWeekly = () => {
  const [habits, setHabits] = useState(() => {
    const storedHabits = localStorage.getItem('habits');
    return storedHabits ? JSON.parse(storedHabits) : [];
  });
 
  const toggleStatus = (habitIndex, day) => {
    const updatedHabits = [...habits];
    updatedHabits[habitIndex].status[day] = !updatedHabits[habitIndex].status[day];
    setHabits(updatedHabits);
    localStorage.setItem('habits', JSON.stringify(updatedHabits));
  };
 
  const getPreviousDates = () => {
    const currentDate = new Date();
    const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
    const previousDates = [];
    for (let i = 6; i >= 0; i--) {
      const prevDate = new Date(currentDate);
      prevDate.setDate(currentDate.getDate() - i);
      const dayOfWeek = days[prevDate.getDay()];
      const date = prevDate.getDate();
      const month = prevDate.getMonth() + 1;
      previousDates.push({ dayOfWeek, date, month });
    }
    return previousDates;
  };
 
  const previousDates = getPreviousDates();
 
  return (
    <div>
      <h4>Weekly Habits Progress:</h4>
      <table className="table table-bordered mt-5">
        <thead>
          <tr>
            <th className="bg-success text-white">Habit</th>
            {previousDates.map((date, index) => (
              <th className="bg-success text-white" key={index}>
                {date.dayOfWeek} - {date.date}/{date.month}
              </th>
            ))}
          </tr>
        </thead>
        <tbody>
          {habits.map((habit, habitIndex) => (
            <tr key={habitIndex}>
              <td>
                <h4>{habit.name}</h4>
                <small>{habit.description}</small>
              </td>
              {Object.keys(habit.status).map((day) => (
                <td key={day} className="text-center" style={{ cursor: 'pointer' }} onClick={() => toggleStatus(habitIndex, day)}>
                  {habit.status[day] ? (
                    <FaCheck className="text-success" title="Mark undone" size={40} />
                  ) : (
                    <FaTimes className="text-danger" title="Mark Done" size={40} />
                  )}
                </td>
              ))}
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
};
 
export default ViewWeekly;


Start your application using the following command.

npm start

Output: Open web-browser and type the following URL http://localhost:3000/

gfg_habit_tracker



Like Article
Suggest improvement
Previous
Next
Share your thoughts in the comments

Similar Reads