Skip to main content

Overview

Been is built on a modern React stack with a focus on simplicity, performance, and type safety. The application uses a declarative architecture with unidirectional data flow, making it predictable and easy to reason about.

Technology Stack

The application is built with three primary technologies:

React

Component-based UI library for building the interface

Jotai

Atomic state management for handling application state

Mapbox GL

Interactive 3D map rendering and visualization

Core Dependencies

From package.json, the main runtime dependencies are:
{
  "react": "^19.0.0",
  "react-dom": "^19.0.0",
  "jotai": "^2.0.0",
  "mapbox-gl": "~3.19.0",
  "react-map-gl": "^8.0.0",
  "tailwindcss": "^4.0.0"
}

Application Structure

The application follows a modular component architecture:
src/
├── components/        # React components
│   ├── app.tsx       # Root application component
│   ├── globe.tsx     # Map visualization component
│   ├── menu.tsx      # Country selection menu
│   └── menu-item.tsx # Individual country item
├── state/            # State management
│   └── atoms.ts      # Jotai atoms and derived state
├── models/           # TypeScript interfaces
│   ├── country.ts    # Country data model
│   └── region.ts     # Region data model
├── utils/            # Utility functions
│   └── regionalizer.ts # Country grouping logic
└── data/             # Static data
    └── countries.ts  # Country definitions

Component Hierarchy

The application uses a simple, flat component hierarchy:
<App>
  ├── <Menu>
  │   └── <MenuItem /> (multiple)
  └── <Globe>
      ├── <Source /> (Mapbox data)
      └── <Layer /> (visualization layers)

Root Component

The App component (src/components/app.tsx) is the entry point that orchestrates the entire application:
export const App: FC = memo(() => {
  const setRawCountries = useSetAtom(rawCountriesAtom);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>();

  useEffect(() => {
    import('../data/countries')
      .then(({ countries }) => {
        const countryMap = Object.fromEntries(
          countries.map((c) => [c.iso3166, c]),
        );
        setRawCountries(countryMap);
        setLoading(false);
      })
      .catch((error) => {
        setError(error);
      });
  }, [setRawCountries]);

  return (
    <div className="grid size-full grid-rows-[auto,1fr,auto] md:grid-cols-3">
      {/* Header */}
      <div className="flex items-center justify-center bg-primary">
        <h1>been</h1>
      </div>
      
      {/* Menu sidebar */}
      <div className="flex flex-col overflow-auto">
        <Menu loading={loading} />
      </div>
      
      {/* Globe/Map */}
      <div className="order-2 min-h-[60vh] md:col-span-2">
        <Globe />
      </div>
    </div>
  );
});

Data Flow

Been implements unidirectional data flow using Jotai atoms:
1

Data Loading

Country data is loaded asynchronously and stored in the rawCountriesAtom
2

User Interaction

User selects/deselects countries through the Menu component
3

State Update

Write-only atoms (addCountryAtom, removeCountryAtom) update the selection state
4

Derived State

Computed atoms (countriesAtom, regionsAtom) automatically recalculate
5

UI Update

Components re-render with updated data, Globe visualization updates

Type Safety

Been uses TypeScript with strict type checking enabled. Core data models are defined as interfaces:
// src/models/country.ts
export interface Country {
  name: string;
  iso3166: string;
  region: string;
  bounds?: [number, number, number, number];
  selected?: boolean;
}

// src/models/region.ts
export interface Region {
  name: string;
  values: Country[];
  complete?: number;
}

Build and Development

The application uses Vite as its build tool for fast development and optimized production builds:
{
  "scripts": {
    "start": "vite --host",
    "build": "tsc --noEmit && vite build",
    "preview": "vite preview --host"
  }
}
The build process performs TypeScript type checking before creating optimized bundles. This ensures type safety in production.

Performance Optimizations

Memoization

All components use memo() to prevent unnecessary re-renders:
export const Globe = memo(
  forwardRef<MapForwardedRef>((_, ref) => {
    // Component implementation
  })
);

Code Splitting

Country data is loaded asynchronously using dynamic imports:
import('../data/countries').then(({ countries }) => {
  // Process countries
});

Computed State

Jotai’s derived atoms ensure calculations only happen when dependencies change:
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),
    }),
  );
});

Next Steps

State Management

Learn how Jotai atoms manage application state

Map Integration

Explore how Mapbox GL powers the visualization

Build docs developers (and LLMs) love