What is Metro?
Discord’s mobile app is a React Native bundle: thousands of Metro modules packed into one file. Nothing is global. Every store, component, and helper lives behind an opaque numeric module id. To change how something behaves, you first have to find the exact module that owns it. That is whatmetro does. It walks the registry and hands you back the module matching your description. It’s also the layer everything else builds on: assets, i18n, the common handles, flux stores, and your own addons all reach for Metro first.
Metro is the foundation. If you only learn one module, learn this one. The patterns here recur everywhere.
Finding modules
You rarely know a module’s id, but you usually know its shape. Metro gives you afindBy* helper for each kind of fingerprint a module leaves behind.
- By props
- By name
- By store
Match a module by the properties it exposes. The most common search by far.
| Helper | Matches on |
|---|---|
findByProps(...names) | Properties present on the export |
findByName(name) | The name of the default export |
findByDisplayName(name) | The export’s displayName |
findByFilePath(path) | The source file a module was imported from |
findByPrototypes(...names) | Methods present on the export’s prototype |
findStore(name) | A flux store’s registered name |
On mobile, most components are matched by their
name, so findByName is the one you’ll reach for. findByDisplayName exists for the cases where a component sets a displayName, but those are less common here than on desktop.Filters
EveryfindBy* helper is sugar over a filter: a predicate that returns true for the module you want. They live on metro.filters, and when the built-in helpers don’t fit a shape you can build your own and hand it to find (or findLazy).
Lazy resolution & caching
Two things keep startup fast. Caching. Every filter carries a cache key, so a successful search is remembered. The second time you search for the same shape, Metro skips the registry walk and resolves from cache. Lazy resolution. Pass{ lazy: true } (or use a *Lazy wrapper) to get back a proxy instead of running the search now. The search runs the first time you touch the result, so a module you reference at the top of a file but only use inside a rarely-called function costs nothing until then.
Prefer lazy for module handles you declare at module scope. It’s why Unbound can reference dozens of modules without paying for all of them at launch.
Caching custom filters
ThefindBy* helpers build their cache key by serializing their arguments, so their results are remembered across cold starts. A raw filter function you write yourself has no such key, so Metro can’t cache it: every cold start re-runs your predicate against the whole registry, which is the one search shape that stays slow.
createCacheable fixes that. Wrap your filter and give it a serialization of its arguments, and Metro caches the result under that key just like a built-in filter.
Common modules & stores
The modules everyone needs (React, React Native, the dispatcher, constants, the big flux stores) are pre-resolved for you. Reach for these instead of re-finding them.metro.common: shared modules
metro.common: shared modules
metro.stores: flux stores
metro.stores: flux stores
Pre-resolved flux stores such as
Users, Guilds, and Theme.metro.api: request helpers
metro.api: request helpers
Action-creator modules like
Messages, Linking, and Profiles for driving the app.