Skip to main content

Overview

Been uses Jotai for state management, a primitive and flexible state management library for React. Jotai uses atomic state units that can be composed together, providing a simple yet powerful approach to managing application state.
Jotai was chosen over other state management solutions like Redux or Zustand because of its minimal boilerplate, excellent TypeScript support, and atomic approach that scales well with React’s concurrent features.

State Architecture

All application state is defined in src/state/atoms.ts. The state is organized into three categories:
  1. Base Atoms - Primitive state containers
  2. Derived Atoms - Computed state based on other atoms
  3. Write-Only Atoms - Action handlers for state mutations

Base Atoms

These atoms hold the core application state:

Raw Countries Atom

Stores the complete country dataset as a dictionary for fast lookups:
export const rawCountriesAtom = atom<Record<string, Country>>({});
Why a dictionary? Using Record<string, Country> with ISO 3166 codes as keys enables O(1) lookup performance when checking country data.

Selected Countries Atom

Persists the user’s country selections to localStorage:
import { atomWithStorage } from "jotai/utils";

const COUNTRIES_STORAGE_KEY = "APP_COUNTRIES";

export const selectedCountriesAtom = atomWithStorage<readonly string[]>(
  COUNTRIES_STORAGE_KEY,
  [],
  undefined,
  { getOnInit: true },
);
Key features:
  • Uses atomWithStorage from jotai/utils for automatic persistence
  • Stores only ISO 3166 country codes (not full country objects) to minimize storage size
  • getOnInit: true ensures data is loaded synchronously on first render
  • Immutable array type (readonly string[]) prevents accidental mutations

Focus Atom

Tracks the currently focused country for map navigation:
export const focusAtom = atom<Country | null>();
This atom is used by the Globe component to pan/zoom to selected countries.

Derived Atoms

Derived atoms compute values based on other atoms and automatically update when dependencies change.

Countries Atom

Combines raw country data with selection state:
export const countriesAtom = atom<readonly Country[]>((get) => {
  const rawCountries = get(rawCountriesAtom);
  const selectedCountries = get(selectedCountriesAtom);

  return Object.values(rawCountries).map((c) =>
    Object.assign(c, {
      selected: selectedCountries.includes(c.iso3166),
    }),
  );
});
How it works:
  1. Reads both rawCountriesAtom and selectedCountriesAtom
  2. Transforms the country dictionary into an array
  3. Enriches each country with its selected status
  4. Automatically recalculates when either dependency changes

Regions Atom

Groups countries by region and calculates completion percentages:
export const regionsAtom = atom<readonly Region[]>((get) => {
  const countries = get(countriesAtom);
  return regionalizer(countries);
});
This atom depends on countriesAtom, creating a dependency chain:
rawCountriesAtom ──┐
                   ├──> countriesAtom ──> regionsAtom
selectedCountriesAtom ──┘
Derived atoms in Jotai are lazy - they only compute when a component reads them. This makes the state system extremely efficient.

Write-Only Atoms (Actions)

Write-only atoms handle state mutations with complex logic.

Add Country Atom

Adds a country to the selection and focuses the map on it:
export const addCountryAtom = atom(
  undefined,
  (get, set, countryCode: string) => {
    const selectedCountries = get(selectedCountriesAtom);
    if (!selectedCountries.includes(countryCode)) {
      const countries = get(countriesAtom);
      const focusCountry =
        countries.find((c) => c.iso3166 === countryCode) ?? undefined;
      set(focusAtom, focusCountry);
      set(selectedCountriesAtom, [...selectedCountries, countryCode]);
    }
  },
);
Implementation details:
  • First parameter undefined means this atom has no read value
  • Second parameter is the write function that receives get, set, and parameters
  • Checks for duplicates before adding
  • Sets focus atom to trigger map navigation
  • Creates a new array (immutability) when updating selections

Remove Country Atom

Removes a country from the selection:
export const removeCountryAtom = atom(
  undefined,
  (get, set, countryCode: string) => {
    const selectedCountries = get(selectedCountriesAtom);
    set(focusAtom, undefined);
    set(
      selectedCountriesAtom,
      selectedCountries.filter((c) => c !== countryCode),
    );
  },
);

Usage in Components

Components interact with atoms using Jotai hooks:

Reading State

Use useAtomValue to read atom values:
import { useAtomValue } from 'jotai';
import { regionsAtom } from '../state/atoms';

export const Menu: FC = () => {
  const regions = useAtomValue(regionsAtom);
  
  return (
    <ul>
      {regions.map((region) => (
        <li key={region.name}>{region.name}</li>
      ))}
    </ul>
  );
};

Writing State

Use useSetAtom for write-only atoms (avoids unnecessary re-renders):
import { useSetAtom } from 'jotai';
import { addCountryAtom, removeCountryAtom } from '../state/atoms';

export const MenuItem: FC<Props> = ({ country }) => {
  const addCountry = useSetAtom(addCountryAtom);
  const removeCountry = useSetAtom(removeCountryAtom);

  const toggleCountry = useCallback(() => {
    if (country.selected) {
      removeCountry(country.iso3166);
    } else {
      addCountry(country.iso3166);
    }
  }, [country, addCountry, removeCountry]);

  return (
    <input
      type="checkbox"
      checked={country.selected}
      onChange={toggleCountry}
    />
  );
};
Performance tip: Use useSetAtom instead of useAtom when you only need to write to an atom. This prevents the component from re-rendering when the atom value changes.

State Flow Example

Here’s what happens when a user selects a country:
1

User clicks checkbox

The MenuItem component’s toggleCountry callback is invoked
2

Action atom is called

Either addCountryAtom or removeCountryAtom is called with the country code
3

Multiple atoms update

// In addCountryAtom:
set(focusAtom, focusCountry);  // Triggers map navigation
set(selectedCountriesAtom, [...selectedCountries, countryCode]);  // Persists to localStorage
4

Derived atoms recalculate

  • countriesAtom recalculates to include the new selection status
  • regionsAtom recalculates to update completion percentages
5

Components re-render

  • Menu updates to show the checked checkbox
  • Globe updates to highlight the selected country
  • Progress indicators update to reflect new completion percentages

Benefits of This Approach

No reducers, action types, or dispatch functions needed. Define atoms and use them directly.
Jotai automatically tracks which atoms depend on others. When a base atom changes, all dependent atoms recalculate.
Only components that read a specific atom re-render when that atom changes. Fine-grained reactivity without manual optimization.
Full TypeScript support with proper type inference. The compiler catches type errors at build time.
Atoms are just functions that can be tested in isolation without complex setup.

Persistence Strategy

The selectedCountriesAtom uses atomWithStorage to persist user selections:
export const selectedCountriesAtom = atomWithStorage<readonly string[]>(
  COUNTRIES_STORAGE_KEY,  // localStorage key: "APP_COUNTRIES"
  [],                      // default value
  undefined,              // use default storage (localStorage)
  { getOnInit: true },    // load synchronously on init
);
Storage format:
["US", "GB", "FR", "DE", "JP"]
Only ISO 3166 country codes are stored, not full country objects. This keeps the localStorage footprint minimal while still preserving all necessary state.

Jotai Documentation

Official Jotai documentation and API reference

Map Integration

Learn how state drives the map visualization

Build docs developers (and LLMs) love