Use Local Storage in React using hooks by building a progress tracker like Next.js' course page

Last updated on June 22, 2022

If you want a more beginner level tutorial regarding Local Storage, check out my other tutorial where I use plain Javascript and HTML.

What we’ll build in order to utilise Local Storage

Couple of days ago I found a great use case for Local Storage that is being used in production. The Next.js website has section dedicated for short courses with multiple articles, sub articles, quizzes. In addition, every time you view an article and answer a quiz, your score increases.

While you go through the course, the website state update which helps us to remember our progress through the course by visually indicating which articles we have already read.

They record this information via Local Storage! Here’s the JSON that I got after I visited the Local Storage tab in the developer tools.

{
  "ready": true,
  "basics/create-nextjs-app": { "visited": true, "checked": true },
  "basics/create-nextjs-app/setup": { "visited": true, "checked": true },
  "basics/create-nextjs-app/welcome-to-nextjs": {
    "visited": true,
    "checked": true
  },
  "basics/navigate-between-pages": { "visited": true }
}

Let’s explore how we can replicate this functionality using React, Local Storage and Hooks. We will have a sidebar of lessons for our course and every time a user reads a lesson, we will record it in our local storage so we can show that they have read it.

Note: For simplicity reasons, we are going to omit the visited and checked states and use only visited instead.

Step 1: Set up the test data

Let’s begin by setting up a basic data for our course which will include 3 lessons. Each object will have an id and a name which represents a lesson.

// data.js

const data = [
  {
    id: "first-lesson",
    name: "First Lesson",
  },
  {
    id: "second-lesson",
    name: "Second Lesson",
  },
  {
    id: "third-lesson",
    name: "Third Lesson",
  },
];

export default data;

In this tutorial, we are using Next.js and TailwindCSS

Step 2: Set up routes and Local Storage

Step 2.1: Create a Layout.js file and set up Local storage

This file will act as a wrapper for our course pages. It does several things:

import data from "../data";
import { useState, useEffect } from "react";
import Check from "./Check";

const Layout = ({ children }) => {
  const [progress, setProgress] = useState({});

  useEffect(() => {
    let progress = JSON.parse(localStorage.getItem("progress")) || {};
    setProgress(progress);
  }, []);

  return (
    <div className="my-20 max-w-5xl mx-auto px-8">
      <h1 className="text-center font-bold text-3xl">Local storage in React</h1>
      <div className="grid grid-cols-5 gap-8 mt-12">
        <ul className="col-span-2 bg-blue-100 p-4 rounded-lg">
          {data.map((lesson) => {
            return (
              <li key={lesson.id}>
                <a
                  href={`/course/${lesson.id}/`}
                  className="hover:underline font-semibold flex w-full justify-between items-center gap-2 hover:text-gray-700"
                >
                  <span>- {lesson.name}</span>

                  {progress.hasOwnProperty(lesson.id) &&
                  progress[lesson.id].visited ? (
                    <Check />
                  ) : null}
                </a>
              </li>
            );
          })}
        </ul>
        <div className="col-span-3 border border-dashed border-gray-300 p-8 rounded-lg">
          <div>
            <h2 className="text-2xl font-bold mb-4">Content</h2>
            <div>{children}</div>
          </div>
        </div>
      </div>
    </div>
  );
};

export default Layout;

2.2 Create individual lesson pages

// pages/course/[id].js

import { useEffect } from "react";
import data from "../../data";
import Layout from "../../components/Layout";

const Lesson = ({ lesson }) => {
  // When you visit a lesson, mark it as visited
  useEffect(() => {
    // either create a new item or initialize a new one if the user doesn't have it
    let progress = JSON.parse(localStorage.getItem("progress")) || {};

    progress[lesson.id] = { visited: true };

    localStorage.setItem("progress", JSON.stringify(progress));
  }, [lesson.id]);

  return <Layout>{lesson.name}</Layout>;
};

export const getStaticProps = async ({ params }) => {
  const lesson = data.find((item) => item.id === params.id);
  return {
    props: { lesson },
  };
};

export const getStaticPaths = async () => {
  return {
    paths: data.map((item) => {
      return { params: { id: item.id } };
    }),
    fallback: false,
  };
};

export default Lesson;

And voilà - we have a fully functioning tracking for our course. If the user has viewed the lesson, we will showcase a check icon next to this lesson with in the sidebar.

Step 3. Use packages to make your life easier

In this tutorial, we set up the local storage logic by ourselves using the useState and useEffect hooks. Thankfully, the React community is awesome and they have some great packages which wraps all the functionality you need in useful custom hooks.

Here are some of my favourites:

Next steps

You now know how to use Local Storage in React. Are you interested in improving your knowledge further on? Grab the source code of this project from Github and complete the following tasks

  1. Ability to have sub lessons
  2. Assign score to each page and total it together when a user visits a specific page
  3. Create a quiz at the bottom of one of the lessons and record the data in local storage, so the user can see that they have answered it and how they have answered it

You are welcome to submit your own repo by leaving a comment below.

Invite us to your inbox.

Articles, guides and interviews about web development and career progression.

Max 1-2x times per month.