Rainbow follows strict coding conventions to maintain code quality, consistency, and performance.
Core Principles
TypeScript First
Write all new files in .ts or .tsx. Remaining JavaScript files are checked against an error baseline (yarn lint:js-types) — don’t regress it.
TypeScript provides type safety and better developer experience. All new code must be written in TypeScript.
No Barrel Exports
Import directly from source files, not index.ts. Barrel files are strictly forbidden.
Barrel files (index.ts that re-export from other files) are not allowed because they:
Defeat tree-shaking
Hide circular dependencies
Trigger cascading module loading
Hurt build performance
Bad:
// ❌ Don't create barrel files
// src/features/wallet/index.ts
export { WalletComponent } from './WalletComponent' ;
export { useWallet } from './useWallet' ;
// ❌ Don't import from barrel files
import { WalletComponent } from '@/features/wallet' ;
Good:
// ✅ Import directly from source files
import { WalletComponent } from '@/features/wallet/WalletComponent' ;
import { useWallet } from '@/features/wallet/useWallet' ;
This rule is enforced by ESLint with a limited allowlist for legacy files.
Type-Only Imports
Use the type annotation for type-only imports to help bundlers optimize:
Good:
import { type UserData } from '@/types/user' ;
import { type ComponentProps } from 'react' ;
Bad:
import { UserData } from '@/types/user' ;
This is enforced by ESLint via the @typescript-eslint/consistent-type-imports rule.
Code Organization
Architecture Migration
The codebase is mid-migration toward domain-organized architecture:
New code : Goes in src/features/ with ui/data/core layer separation
Legacy code : Lives in flat top-level directories (components/, screens/, hooks/, helpers/, utils/)
Example feature structure:
src/features/wallet/
├── ui/ # React components
├── data/
│ └── stores/ # State management
└── core/ # Business logic
Key Directories
src/framework/ — App-agnostic infrastructure (http, safe math, UI primitives)
src/__swaps__/ — Swap feature, aliased as @/swaps in tsconfig
src/graphql/ — Separate yarn workspace for GraphQL codegen
State Management
Rainbow uses custom store creators built on Zustand, defined in src/state/internal/:
Store Types
createRainbowStore
General-purpose store with optional MMKV persistence. Use for client state.
import { createRainbowStore } from '@/state/internal/createRainbowStore' ;
const useSettingsStore = createRainbowStore (
( set ) => ({
theme: 'dark' ,
setTheme : ( theme ) => set ({ theme }),
}),
{
persist: {
name: 'settings' ,
version: 1 ,
},
}
);
createQueryStore
Combines data fetching + state in one store. Reactive $ params auto-refetch when dependencies change. Replaces the React Query + Zustand dual-store pattern. Use for server/async data.
import { createQueryStore } from '@/state/internal/createQueryStore' ;
const useTokenPriceStore = createQueryStore ({
queryKey : ({ tokenAddress }) => [ 'tokenPrice' , tokenAddress ],
queryFn : async ({ tokenAddress }) => {
const response = await fetch ( `/api/price/ ${ tokenAddress } ` );
return response . json ();
},
});
createDerivedStore
Read-only store that composes other stores. Use for computed/aggregated state.
import { createDerivedStore } from '@/state/internal/createDerivedStore' ;
const useTotalBalance = createDerivedStore (
[ useWalletStore , usePricesStore ],
( wallet , prices ) => wallet . tokens . reduce (
( sum , token ) => sum + ( prices [ token . address ] || 0 ),
0
)
);
Legacy Systems
These are still in use but being phased out:
React Query (src/react-query/) — being replaced by createQueryStore
Redux (src/redux/) — only for: charts, contacts, ENS registration, gas, settings
ESLint Configuration
Rainbow uses ESLint with custom rules defined in .eslintrc.js:
{
extends : [ 'rainbow' , 'plugin:yml/standard' ],
rules : {
// Type imports must use inline type annotation
'@typescript-eslint/consistent-type-imports' : [ 'error' , {
prefer: 'type-imports' ,
fixStyle: 'inline-type-imports' ,
}],
// No duplicate imports
'import/no-duplicates' : 'error' ,
// Exhaustive deps for hooks
'react-hooks/exhaustive-deps' : [ 'warn' , {
additionalHooks: '(useDeepCompareEffect|useDeepCompareCallback|...)'
}],
}
}
Restricted Imports
Some imports are restricted to enforce best practices:
// ❌ Don't use StatusBar from react-native
import { StatusBar } from 'react-native' ;
// ✅ Use SystemBars from react-native-edge-to-edge
import { SystemBars } from 'react-native-edge-to-edge' ;
// ❌ Don't import from @react-navigation/core directly
import { useNavigation } from '@react-navigation/core' ;
// ✅ Use @/navigation to ensure customizations are applied
import { useNavigation } from '@/navigation' ;
Pre-commit Hooks
Rainbow uses Husky and lint-staged to enforce quality on commit:
// package.json
{
"lint-staged" : {
"*.{js,jsx,ts,tsx,graphql,yml,yaml}" : [
"prettier --write" ,
"eslint --cache"
]
}
}
Files are automatically formatted and linted when you commit.
TypeScript Configuration
Path Aliases
Rainbow uses path aliases for cleaner imports:
// tsconfig.json paths
{
"@/*" : [ "./src/*" ],
"@/swaps" : [ "./src/__swaps__" ],
"@rainbow-me/config/*" : [ "./src/config/*" ],
"@rainbow-me/entities" : [ "src/entities" ],
"@rainbow-me/routes" : [ "src/navigation/routesNames" ]
}
Usage:
import { ChainId } from '@/chains/types' ;
import { SwapScreen } from '@/swaps' ;
import { config } from '@rainbow-me/config/experimental' ;
Strict Mode
TypeScript strict mode is enabled:
{
"compilerOptions" : {
"strict" : true ,
"target" : "esnext" ,
"module" : "ESNext"
}
}
Avoid Barrel Files
As mentioned above, barrel files hurt performance. Import directly from source files.
Use Memo Wisely
import { useMemo } from 'react' ;
import { use - memo - one } from 'use-memo-one' ;
// Use useMemo for expensive computations
const sortedTokens = useMemo (
() => tokens . sort (( a , b ) => b . balance - a . balance ),
[ tokens ]
);
Lazy Load Heavy Dependencies
// Import heavy libraries lazily
const processImage = async ( imageUri : string ) => {
const ImageManipulator = await import ( 'expo-image-manipulator' );
return ImageManipulator . manipulateAsync ( imageUri , [ ... ]);
};
Verification Commands
Before submitting code, run these verification commands:
# Format check
yarn format:check
# TypeScript type check
yarn lint:ts
# JavaScript type check (against baseline)
yarn lint:js-types
# ESLint
yarn lint:js
# All linting (format + TS + JS)
yarn lint
# Check for circular dependencies
yarn check:cycles
# Run tests
yarn test
Run yarn lint before every commit to catch issues early.
Common Patterns
Component Structure
import { type ReactNode } from 'react' ;
import { View , Text } from 'react-native' ;
import { type StyleProp , type ViewStyle } from 'react-native' ;
interface WalletCardProps {
address : string ;
balance : string ;
style ?: StyleProp < ViewStyle >;
children ?: ReactNode ;
}
export function WalletCard ({
address ,
balance ,
style ,
children
} : WalletCardProps ) {
return (
< View style = { style } >
< Text >{ address } </ Text >
< Text >{ balance } </ Text >
{ children }
</ View >
);
}
Custom Hooks
import { useEffect , useState } from 'react' ;
export function useTokenBalance ( tokenAddress : string ) {
const [ balance , setBalance ] = useState < string >( '0' );
const [ loading , setLoading ] = useState ( true );
useEffect (() => {
let cancelled = false ;
async function fetchBalance () {
try {
setLoading ( true );
const result = await getBalance ( tokenAddress );
if ( ! cancelled ) setBalance ( result );
} finally {
if ( ! cancelled ) setLoading ( false );
}
}
fetchBalance ();
return () => { cancelled = true ; };
}, [ tokenAddress ]);
return { balance , loading };
}
Style Guide Summary
TypeScript Only All new files must be .ts or .tsx
No Barrel Exports Import directly from source files
Type-Only Imports Use import { type ... } for types
Strict Linting Follow ESLint rules strictly