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.
.png)
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:.png)
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/
|