This documentation is specifically for the frontend web/mobile app.
If you're looking for more info on the parsers, check out how to setup Python development environment and how to fix a broken parser (and test it locally).
Prerequisites:
- Ensure you have
NodeJS
(v20.9.0
or above) andpnpm
installed locally (brew install pnpm
) - Run
pnpm install
in both the web and mockserver directories
- Start the mockserver:
pnpm run mockserver
- Run app in another tab:
pnpm dev
pnpm mockserver
- starts the mockserver.pnpm dev
- start a development server with hot reload.pnpm build
- build for production. The generated files will be in thedist
folder.pnpm preview
- locally preview the production build.pnpm test
- run unit and integration tests related to changed files based on git.pnpm cy:e2e
- work with integration tests. Remember to runpnpm dev
before!pnpm cy:components
- work with component tests.pnpm format
- format all files with Prettier.pnpm lint
- runs TypeScript and ESLint.pnpm validate
- runslint
,test:ci
andtest:e2e:ci
.
"test:e2e": "pnpm preview:test 'cypress open'",
"test:e2e:headless": "pnpm preview:test 'cypress run'",
"test:e2e:ci": "vite build && pnpm preview:test 'cypress run --record'",
As an eMap internal team member, you can also run the app connected to production API instead of the mockserver:
- Run
VITE_PUBLIC_ELECTRICITYMAP_PUBLIC_TOKEN='YOUR TOKEN' pnpm dev
- Add a
?remote=true
query parameter
- Add an environment variable for
SENTRY_AUTH_TOKEN="find it here => https://sentry.io/settings/account/api/auth-tokens/"
- Add an environment variable for
VITE_PUBLIC_ELECTRICITYMAP_PUBLIC_TOKEN='YOUR TOKEN'
See how to edit world geometries.
We use Lucide for icons and react-icons for brand icons, brand icons should be imported from the FontAwesome 6 library.
If an icon is missing reach out the to Electricity Maps team either on Slack or by creating an issue. There is also the possibility to file a icon request upstream in Lucides GitHub repository or creating and submitting one yourself as long as it matches the Lucide visual style and guidelines.
Search for icons here: Lucide Search for brand icons here: Font Awesome Brands
Note
There might be some left over icon usage that don't use Lucide, feel free to replace them with Lucide icons when they are matching visually.
We use jotai for global UI state management, TanStack Query for data state management and React's setState() for local component state.
There is no “right” answer for this. Some users prefer to keep every single piece of data in jotai, to maintain a fully serializable and controlled version of their application at all times. Others prefer to keep non-critical or UI state, such as “is this dropdown currently open”, inside a component's internal state. (Original link, visited 2022)
The following list can be used to determine whether to store in atom or setState.
- Do other parts of the application care about this data?
- Do you need to be able to create further derived data based on this original data?
- Is the same data being used to drive multiple components?
- Do you want to persist the data for another session?
- Do you want to cache the data (ie, use what's in state if it's already there instead of re-requesting it)?
Choosing atoms
-
States that drastically changes the behavior of the map and is important when sharing with other people. Examples could be production/consumption and country/zones view. This state should be stored in the URL and localstorage. Use atomWithCustomStorage.
-
States that personalizes the app but is not essential to be shared. For example dark-mode and color-blind mode. This state should be stored in localstorage. use atomWithStorage
-
State that changes the viewer experience and may be disrupted by a reload but is not essential to be shared. This state should be stored in sessionStorage. Use atomWithStorage.
-
All other global state should be able to use the basic atom.
Avoid prop drilling with global state
In general all global atoms should be accessed through useAtom and not through prop drilling. This allows our components to be more modular and takes full advantage of the global nature of atoms. In general we want to avoid transporting props multiple levels down.
If the components are very closely related it may make sense to pass down derived values instead.
- ButtonGroup.tsx
- Button1.tsx
- Button2.tsx
- Button3.tsx
Here the buttonGroup may retrieve a state 'globalSelectedValue' from the global state. Instead of passing down 'globalSelectedValue' to the buttons, the buttonGroup can pass down a 'isSelected' boolean prop which depends on 'globalSelectedValue' and the value of the individual buttons.