DAY 16 : How I Built 3 Practical React Projects: Weather App, Bookmark Saver & Theme Toggle | My Web Dev Journey - ReactJS


Introduction
Welcome to Day 16 of my web development journey!
For the past few weeks, I’ve been diving into ReactJS after completing the fundamentals of HTML, CSS, and JavaScript.
I implemened Dark/Light Mode using Context API and Build two projects i.e. Bookmark Saver and Weather App which helps me to understand concepts like useState
, useEffect
, Conditional Rendering
, Context API
, Custom Hooks
.
I'm sharing my learning process in public to stay consistent and hopefully help others who are on a similar path.
Here’s what I learnt in last 3 days:
Day 13:
→ Dark/Light Mode Implementation using Context APIDay 14:
→ Build Bookmark Saver Project
→ Implemented state using Context API
-> Make Custom Hook to use Context easilyDay 15:
→ Build Weather App Project
→ Fetch Data from OpenMapWeather API
-> Conditonally Rendering home, result, error
Let’s break down each of these topics below 👇
1. Dark/Light Mode Implementation using Context API:
I'm sharing how I implemented Dark/Light Mode in a React application using Context API.
It’s a common feature in modern web apps and a great use case for learning React’s Context and state management.
Why Use Context API for Theme?
We could lift the state up and pass theme data as props
, but it becomes messy if we have to pass it down through multiple components. That’s where Context API comes in — it allows us to share state like the current theme
across components without prop drilling
.
Folder Structure:
src/
│
├── components/
│ └── Header.jsx
│
├── context/
│ └── ThemeContext.jsx
│
├── App.jsx
└── index.css
Step-by-Step Implementation:
1. Create a ThemeContext
// src/context/ThemeContext.jsx
import { createContext, useContext, useState } from "react";
const ThemeContext = createContext();
export const ThemeProvider = ({ children }) => {
const [darkMode, setDarkMode] = useState(false);
const toggleTheme = () => setDarkMode(prev => !prev);
return (
<ThemeContext.Provider value={{ darkMode, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
};
export const useTheme = () => useContext(ThemeContext);
Here, we created:
A
ThemeContext
A
ThemeProvider
that wraps the entire appA
useTheme()
custom hook for easier access
2. Wrap App with ThemeProvider
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { ThemeProvider } from './ThemeContext';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<ThemeProvider>
<App />
</ThemeProvider>
);
3. Using ThemeContext
in App
// src/App.jsx
import { useTheme } from "./context/ThemeContext";
import Header from "./components/Header";
import "./index.css";
function App() {
const { darkMode } = useTheme();
return (
<div className={darkMode ? "app dark" : "app"}>
<Header />
<main>
<h1>Hello, React World!</h1>
<p>Toggle the theme using the button above</p>
</main>
</div>
);
}
export default App;
4. Theme Toggle Button in Header
// src/components/Header.jsx
import { useTheme } from "../context/ThemeContext";
const Header = () => {
const { darkMode, toggleTheme } = useTheme();
return (
<header>
<h2>My Web Dev App</h2>
<button onClick={toggleTheme}>
Switch to {darkMode ? "Light" : "Dark"} Mode
</button>
</header>
);
};
export default Header;
5. Add Styles for Light
and Dark Mode
/* src/index.css */
body {
margin: 0;
font-family: sans-serif;
transition: background 0.3s ease;
}
.app {
background-color: #ffffff;
color: #000000;
min-height: 100vh;
padding: 2rem;
}
.app.dark {
background-color: #121212;
color: #ffffff;
}
button {
padding: 0.5rem 1rem;
cursor: pointer;
}
Result:
We can now toggle between light
and dark
mode globally. The state is shared across all components using Context API — no props passed manually!
Key Takeaways:
Context API is perfect for global state like themes, language, or user data.
useContext()
simplifies access to context values.We can pair it with
useState()
or evenuseReducer()
for more complex logic.
Final Thoughts:
Implementing a dark/light mode
using React’s Context API taught me how powerful and clean global state management can be without using external libraries.
2. Bookmark Saver Project:
After learning React basics like useState
, useEffect
, Context API
, and custom hooks
, I wanted to build something useful and realistic. That’s how I decided to make Bookmark Saver App. It allows users to save, manage, and delete their favorite website links — all stored in the browser using localStorage
.
Here, I’ll walk you through how I planned and implemented the project using React best practices, including the Context API, custom hooks, and utility functions.
Features:
Add bookmarks with a name and URL
Display a list of saved bookmarks
Remove bookmarks individually with confirmation
Input validation to prevent empty fields and invalid URLs
Prevent duplicate bookmarks
Persistent storage using localStorage
Global state management using React Context API and custom hooks
Folder Structure:
src/
│
├── Components/
│ ├── App.js
│ ├── Home.js
│ ├── InputContainer.js
│ ├── InputBox.js
│ ├── Button.js
│ ├── BookmarkList.js
│ └── BookmarkItem.js
│
├── contexts/
│ └── BookmarkContext.js
│
├── hooks/
│ └── useBookmark.js
│
├── utils/
│ ├── createBookmarkUtils.js
│ └── initialBookmarksUtils.js
│
└── index.js
React Concepts Applied:
useState
– to manage local component statesuseEffect
– to sync with localStorageContext API
– for global state managementCustom Hook
– to simplify context usageUtility functions
– for reusable logic
Implementation:
1. Setting Up Context API:
In BookmarkContext.jsx
:
export const BookmarkContext = createContext();
export const BookmarkProvider = ({ children }) => {
const [allBookmarks, setAllBookmarks] = useState(getInitialBookmarks);
return (
<BookmarkContext.Provider value={{ allBookmarks, setAllBookmarks }}>
{children}
</BookmarkContext.Provider>
);
};
2. Custom Hook (useBookmark.js
):
export const useBookmark = () => useContext(BookmarkContext);
Using this hook, any component can access the bookmark state easily.
3. Local State:
In Home.jsx
:
// Local State
const [bookmarkName, setBookmarkName] = useState("");
const [bookmarkUrl, setBookmarkUrl] = useState("");
const [error, setError] = useState("");
// Global State
const { allBookmarks, setAllBookmarks } = useBookmark();
Here,
bookmarkName
: stores the current value of the bookmark name input field.bookmarkUrl
: stores the current value of the bookmark URL input field.error
: holds the error message string if input validation fails.
4. Fetching Initial Bookmarks:
In utils/initialBookmarksUtils.js
:
export const getInitialBookmarks = () => {
const saved = localStorage.getItem("bookmarks");
return saved ? JSON.parse(saved) : [];
};
This ensures the app starts with previously saved bookmarks from localStorage
.
5. Create New Bookmark:
In utils/createBookmarkUtils.js
:
export const createBookmark = (name, url, existingBookmarks) => {
if (!name.trim() || !url.trim())
return { error: "Input field cannot be empty" };
if (!url.startsWith("http://") && !url.startsWith("https://")) {
return { error: "Invalid URL format" };
}
const isDuplicate = existingBookmarks.some((b) => b.url === url);
if (isDuplicate) return { error: "Duplicate bookmark" };
return {
bookmark: {
name: name.trim(),
url: url.trim(),
},
};
};
6. Add New Bookmark:
In Home.jsx
:
function handleAddBookmark() {
const result = createBookmark(bookmarkName, bookmarkUrl, allBookmarks);
if (result.error) {
setError(result.error);
return;
}
setError("");
setAllBookmarks((prev) => [...prev, result.bookmark]);
setBookmarkName("");
setBookmarkUrl("");
}
7. Syncing with localStorage:
In Home.jsx
:
useEffect(() => {
localStorage.setItem("bookmarks", JSON.stringify(allBookmarks));
}, [allBookmarks]);
This makes sure bookmarks persist between reloads without any backend.
8. Deleting Bookmarks:
In BookmarkItem.jsx
:
const { allBookmarks, setAllBookmarks } = useBookmark();
function handleRemoveBookmark() {
const confirmDelete = confirm(`Remove bookmark "${name}"?`);
if (!confirmDelete) return;
const filteredBookmarks = allBookmarks.filter(
(bookmark) => bookmark.url !== url
);
setAllBookmarks(filteredBookmarks);
}
This ensures smooth and instant UI update on deletion.
What I Learned:
How to centralize state using
Context API
Using custom hooks to simplify code reuse
Effective form validation and state reset
Storing and syncing data with
localStorage
Final Thoughts:
This project gave me a great opportunity to deepen my understanding of React’s state management and real-world data handling without a backend. It also improved my confidence with:
Reusability of code (
utils
,context
,custom hooks
)Managing form and list data
Building a clean UI with minimal dependencies
3. Weather App Project:
After building a few beginner-friendly projects with React, I wanted to challenge myself with a real-world use case — something that deals with APIs, handles errors, and updates the UI based on user input.
So, I built a Weather App that fetches real-time weather data for any city using the OpenWeatherMap API. The app is fully functional, responsive, and built using just React’s core features — no external state libraries.
Features:
Search for any city and get current weather data.
Shows temperature, weather condition, humidity, and wind speed.
Handles loading and error states gracefully.
Displays custom weather icons based on condition.
Responsive and clean UI, works well on all devices.
Folder Structure:
src/
├── Components/
│ ├── ContentContainer.jsx
│ ├── Error.jsx
│ ├── Home.jsx
│ ├── SearchInput.jsx
│ └── Weather.jsx
├── App.jsx
└── index.js
React Concepts Applied:
useState()
for managing input, weather, and error states.useEffect()
for fetching data on city change.Conditional Rendering
to show weather, loading, or error states.Basic API integration (GET request) with
fetch()
.Component-based architecture for separation of concerns.
Code Walkthrough:
1. App.jsx
– Central Logic
This is the root component that controls state and API fetching:
const [inputCity, setInputCity] = useState("");
const [city, setCity] = useState("");
const [weatherData, setWeatherData] = useState(null);
const [error, setError] = useState(false);
const [loading, setLoading] = useState(false);
Here,
inputCity
: controlled input value for the search box.city
: triggers the API call when submitted.weatherData
: stores weather data from the API.error
: handles invalid or failed responses.loading
: shows loader while data is being fetched.
The useEffect()
hook fires whenever city
is updated:
useEffect(() => {
if (city) {
fetch(`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=ded956a99e83cfa92e3509a573a6b09c&units=metric`)
.then(res => res.json())
.then(data => {
if (!data || data.cod === "404") {
setError(true);
setLoading(false);
return;
}
setWeatherData(data);
setLoading(false);
})
.catch(err => {
setError(true);
setLoading(false);
});
}
}, [city]);
2. SearchInput.jsx
– Input Component
<input
value={inputCity}
onChange={onChange}
placeholder="Search City"
onKeyDown={(e) => e.key === "Enter" && handleSearch()}
/>
<i onClick={handleSearch} className="fa-solid fa-magnifying-glass"></i>
This component is fully controlled via props.
Searches can be triggered by clicking the icon or pressing Enter.
3. handleSearch
Function
In App.jsx
function handleSearch() {
const trimmedCity = inputCity.trim();
if (!trimmedCity) return;
setCity(trimmedCity);
setWeatherData(null);
setError(false);
setLoading(true);
}
4. ContentContainer.jsx
– Conditionally Rendering
Renders different components based on app state:
{loading && <p>Loading...</p>}
{!loading && !weatherData && !error && <Home />}
{!loading && error && <Error />}
{!loading && weatherData && <Weather weatherData={weatherData} />}
5. Weather.jsx
– Displays Weather Info
Shows city name, date, temperature, weather condition, humidity, and wind:
const Weather = ({ weatherData }) => {
function getDateDay() {
const today = new Date();
const options = { weekday: "short", month: "long", day: "numeric" };
const formattedDate = today.toLocaleDateString("en-US", options);
return formattedDate;
}
function getStatus(status) {
const iconMap = {
clear: clearImg,
clouds: cloudsImg,
rain: rainImg,
snow: snowImg,
thunderstorm: thunderstormImg,
drizzle: drizzleImg,
};
return iconMap[status.toLowerCase()] || weatherImg;
}
return (
<div className="weather-details">
<div className="city-date">
<p className="city">
<i className=" fa-solid fa-location-dot" />
<span className="city-name">{weatherData?.name}</span>
</p>
<p className="date">{getDateDay()}</p>
</div>
<div className="temperature-container">
<div className="weather-img">
<img
src={getStatus(weatherData.weather[0].main)}
alt={weatherData.weather[0].main}
/>
</div>
<div className="weather-status">
<p className="temperature">
{weatherData?.main?.temp} <sup>o</sup>C
</p>
<p className="status">{weatherData?.weather[0]?.main}</p>
</div>
</div>
<div className="humidity-wind-container">
<div className="humidity-container">
<i className="humid-wind-icon fa-solid fa-droplet" />
<div className="humidity-box">
<p className="humidity">Humidity</p>
<p className="humid-percentage speed-percentage ">
{weatherData?.main?.humidity}%
</p>
</div>
</div>
<div className="wind-container">
<i className="humid-wind-icon fa-solid fa-wind" />
<div className="wind-box">
<p className="wind">Wind Speed</p>
<p className="wind-speed speed-percentage">
{weatherData?.wind?.speed}M/s
</p>
</div>
</div>
</div>
</div>
);
};
6. Home.jsx
– Default Screen
<img src={searchCity} alt="search-city" />
<h2>Search City</h2>
<p>Find out the weather conditions of the city</p>
7. Error.jsx
– Error UI
When a city is not found or API fails:
<img src={errorImg} alt="error" />
<h2>City Not Found</h2>
<p>Please Enter Correct City Name</p>
Key Takeaways:
I learned how to manage multiple states like input, data, loading, and error.
Practiced how to structure a real app with reusable components.
Understood how to conditionally render content in React.
Learned API integration and error handling in a real project.
Learned how to visually debug using
console.log
during data fetching.
Final Thoughts:
Building this Weather App helped me understand how to connect UI with real-world data, and how to manage side effects like API calls using useEffect
.
Even without using context or libraries like Redux, this project shows that you can build functional apps using just core React features.
It’s a great stepping stone for learning API integration, conditional rendering, and handling loading/error states in React.
4. What's next:
I’m continuing this journey and will be:
Posting blogs every 3 days
Also learning DSA using Java — check out my DSA Journey Blog
You can follow my journey on X (Twitter) where I post regular updates.
5. Conclusion:
Writing this blog was more than just sharing code — it was about reflecting on how far I’ve come in my web development journey.
Implementing Dark/Light Mode using Context API taught me the power of global state management without needing third-party libraries.
Building the Bookmark Saver App helped me understand how to break a real-world problem into reusable components, use custom hooks, and keep code clean and organized using utility functions.
Finally, the Weather App pushed me to work with APIs, handle real-time user input, and manage side effects using useEffect
— all with just core React features.
Every project, no matter how small, has helped me build confidence and clarity in React. I’m excited to keep learning, building, and sharing more as I grow as a developer.
Stay tuned as I continue learning and building more exciting projects on my web development journey.
If you're on a similar journey, feel free to reach out or follow along — we’re all in this together.
Subscribe to my newsletter
Read articles from Ritik Kumar directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Ritik Kumar
Ritik Kumar
👨💻 Aspiring Software Developer | MERN Stack Developer.🚀 Documenting my journey in Full-Stack Development & DSA with Java.📘 Focused on writing clean code, building real-world projects, and continuous learning.