Habit Tracker with React, NodeJS and DynamoDB

October 30, 2022

3 min read

Habit Tracker with React, NodeJS and DynamoDB
Watch on YouTube

Let me show you how I made a simple time tracker with React and NodeJS so you can find interesting bits for your projects. It's not technically sophisticated because the goal was to have a functioning MVP of the feature in a few days, so it should be easy to grasp.

Here we have a habit page where you can track habits, create habits, and see the progress over time. We include the "Add habit" button with the page title, and when the user state is ready, we render today's progress and active habits. We pull the user state on app start and cash it in the local storage using react-query.

page

export interface Habit {
  id: string
  name: string
  emoji: string
  color: number
  startedAt: number
  successes: string[]
}

The habit interface has an id, name, emoji, color, and timestamp of start date. To know the days the user executed the habit, we store an array of successes. We use the toHabitDate function to convert the date to a string that will contain the day, month, and year. On the card with today's progress, we take habits from the user's state and show them as a list of habit items. We check if the date is in an array of successes to know if the user has done it today. To update both local and remote states on a change, we use the useTrackHabitMutation. Here we make an optimistic update of the local state and query the GraphQl API.

import { FixedWidthContent } from "components/reusable/fixed-width-content"
import { HStack, VStack } from "ui/Stack"
import { Text } from "ui/Text"
import { PageTitle } from "ui/Text/PageTitle"
import { UserStateOnly } from "user/state/UserStateOnly"

import { ActiveHabits } from "./ActiveHabits"
import { CheckDayHabits } from "./CheckDayHabits"
import { CreateHabit } from "./CreateHabit"
import { HabitIdeas } from "./HabitIdeas"

const title = "Daily Habits"

export const HabitsPage = () => {
  return (
    <FixedWidthContent>
      <PageTitle
        documentTitle={`🧘‍♀️ ${title}`}
        title={
          <HStack alignItems="center" gap={20}>
            <Text>{title}</Text>
            <CreateHabit />
          </HStack>
        }
      />
      <UserStateOnly>
        <HStack wrap="wrap" gap={60} alignItems="start">
          <VStack alignItems="center" gap={8}>
            <CheckDayHabits dayName="Today" date={new Date()} />
            <HabitIdeas />
          </VStack>
          <ActiveHabits />
        </HStack>
      </UserStateOnly>
    </FixedWidthContent>
  )
}

We have four mutations to manage habits: create, update, delete and track. Here, every operation involves updating the user in DynamoDB because habits is a map object in the user-item, with id as a key. We could've stored habits as an array too, but it would be more expensive because we would need to get all the habits every time we want to read a single habit. With Map type, we can execute set and remove operations using the key and take a single habit we need using projection expression.

It would've been more efficient to use a set of strings for dates, yet I didn't want to complicate the code, considering DynamoDB doesn't allow empty sets.

Also, we have the read-only view of active habits on the landing page. To achieve it, I wrap the preview with two providers. LandigUserState with a mock user state, and another provider, telling components we are in a read-only mode.

To calculate the streak, we use recursion, going one day back until the date is missing in the successes array. To show the colored habits map, we go over the last 30 days and render the HabitDay component. It shows either a green or gray rectangle, provides a tooltip on hover, and by clicking on it, you can trigger the value if you forgot to track a habit before.