Pokemon Advanced — the recipe: the whole data layer on PokeAPI
TL;DR. A complete, copy-into-your-project module on a real API (PokeAPI): it shows how to split
the data layer into responsibility files and wire all the bricks together. One domain = one folder,
and the C-form of createSynapse assembles it all. Read it after
basic → dispatcher →
effects as the final assembly; or right away — as a structure template.
The final page of the chain. Every previous section dissected one brick on this same domain — here
they come together into one working module: ApiClient with caching → mappers → storage →
selectors → dispatcher → effects → createSynapse → React. This is the reference for how to split a
data-management layer into responsibility files and copy it into your own project.
Each section below links to the page where the corresponding brick is covered in detail.
Module structure
The whole domain lives in a single pokemon-advanced/ folder, one file = one responsibility:
pokemon-advanced/
pokemon.types.ts — domain types + request-state shape
pokemon.store.ts — initialState (store shape)
pokemon.settings.ts — external settings storage (a dependency)
pokemon.api.ts — ApiClient (endpoints, cache) + response mappers
pokemon.selectors.ts — derived values (class Selectors)
pokemon.dispatcher.ts — intents (class Dispatcher)
pokemon.effects.ts — side-effects on RxJS (class Effects)
pokemon.synapse.ts — assembly via createSynapse({ … }) (C-form)
index.ts — public exports
PokemonAdvancedExample.tsx / PokemonDemo.tsx — UI on top of the synapse
helpers.ts — small presentation utilities (typeColor)Data flow
UI (PokemonDemo)
│ store.actions.loadList() / selectPokemon(id) / setSearchQuery(q) / toggleFavorite(id)
▼
dispatcher (intents) ──► action$ ──► effects (RxJS)
│ apiActions/action/signal │ ofType → validateMap → fromRequest(api)
│ ▼
│ pokemon.api.ts (ApiClient + mappers)
│ │ apiResult(data) → mapListResponse / mapDetailsResponse
▼ ▼
└──────────► applyPokemonList / applyPokemonDetails / loadList.success ──► storage
│
selectors (filteredList, isLoading…) ◄┘
│ useSelector
▼
UIThe flow is one-way: the UI sends intents to the dispatcher, effects do side-effects and write the result to storage via actions, and selectors hand derived values back to the UI.
State holds both domain data and the request protocol (api.listRequest/detailsRequest with a
status). More on the request-state shape in create-synapse-effects.
export type ApiStatus = 'idle' | 'loading' | 'success' | 'error' | 'reset'
export interface ApiRequestState {
status: ApiStatus
error: string | null
}
export interface PokemonState {
api: {
listRequest: ApiRequestState
detailsRequest: ApiRequestState
}
pokemonList: PokemonBrief[]
offset: number
hasMore: boolean
selectedPokemonId: number | null
selectedPokemon: PokemonDetails | null
searchQuery: string
favorites: number[]
}pokemon.store.ts next to it is just initialState: PokemonState (both requests 'idle', lists empty).
The 5-state request protocol
The core of the dispatcher↔effects link: every request goes through a fixed lifecycle, and the UI
reads it through status selectors.
UI dispatch (loadList) -> status = 'idle' (no UI change)
|
effect: validateMap
|-- validation OK -> loadingAction -> status = 'loading' (spinner)
| |-- API OK -> apiResult(success) -> status = 'success' (data)
| \-- API ERR -> errorAction -> status = 'error' (error)
\-- validation FAIL -> skipAction -> status = 'reset' (no UI flicker)Map: capability → page
| Capability | Module file | Page |
|---|---|---|
| ApiClient (cache/tags), mappers | pokemon.api.ts | api-client |
| storage + selectors, minimal createSynapse | pokemon.store.ts, pokemon.selectors.ts | create-synapse-basic |
| dispatcher (action/signal/apiActions/watcher) | pokemon.dispatcher.ts | create-synapse-dispatcher, dispatcher-detailed |
| effects (validateMap/apiResult/fromRequest) | pokemon.effects.ts | create-synapse-effects |
| dependencies (settingsStorage, async factory) | pokemon.settings.ts, pokemon.synapse.ts | dependencies |
| React: manual lift / provider | PokemonAdvancedExample.tsx | await-synapse, synapse-ctx |
| framework-independent awaiter, SSR fast-path | — | synapse-awaiter |
| event bus between modules | — | event-bus |