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

Ritik KumarRitik Kumar
11 min read

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 API

  • Day 14:
    → Build Bookmark Saver Project
    → Implemented state using Context API
    -> Make Custom Hook to use Context easily

  • Day 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 app

  • A 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 even useReducer() 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 states

  • useEffect – to sync with localStorage

  • Context API – for global state management

  • Custom Hook – to simplify context usage

  • Utility 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" /> &nbsp;
          <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.

0
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.