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:
Data Loading
Country data is loaded asynchronously and stored in the rawCountriesAtom
User Interaction
User selects/deselects countries through the Menu component
State Update
Write-only atoms (addCountryAtom, removeCountryAtom) update the selection state
Derived State
Computed atoms (countriesAtom, regionsAtom) automatically recalculate
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.
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