| name | imports-exports |
| description | WHAT: TypeScript import ordering and export conventions. WHEN: organizing imports, creating barrel exports, reviewing import structure. KEYWORDS: imports, exports, named exports, import type, barrel, index.ts, path aliases, ESLint, ordering. |
Import and Export Standards
Standards for organizing imports and exports in TypeScript/React Native files.
When to Use
Use these standards when:
- Writing new TypeScript/React Native files
- Reviewing code for proper import organization
- Setting up path aliases in tsconfig.json
- Configuring ESLint rules for imports
- Creating barrel exports (index.ts files)
- Refactoring module structure
Core Principles
ESLint-Enforced Import Order
Imports are automatically organized by ESLint with these groups:
- React and React Native - Built-in frameworks
- External libraries - node_modules packages
- Internal path aliases - @libs, @modules, @data-access, @zest
- Parent imports - ../
- Sibling imports - ./
- Type imports - Always last in each group
Between groups: Blank lines required
Within groups: Alphabetized (case-insensitive)
✅ Good:
import { useEffect, useState, useMemo } from 'react';
import { View, ScrollView, TouchableOpacity } from 'react-native';
import { useQuery } from '@tanstack/react-query';
import { useRecipeData } from '@data-access/query';
import { useT9n } from '@libs/localization';
import { useNavigation } from '@libs/navigation';
import { Accordion, Text, useZestStyles } from '@zest/react-native';
import { FAQ_ITEMS } from './constants';
import { stylesConfig } from './styles';
import type { FaqItem } from './types';
❌ Bad:
import { stylesConfig } from './styles';
import { View } from 'react-native';
import type { FaqItem } from './types';
import { useT9n } from '@libs/localization';
import { useQuery } from '@tanstack/react-query';
Why: Automatic organization ensures consistency and makes dependencies scannable.
Named Exports Only
Never use default exports. Always use named exports.
✅ Good:
export const UserProfile = ({ user }: UserProps) => {
return <View>...</View>;
};
export const calculateTotal = (items: Item[]) => {
return items.reduce((sum, item) => sum + item.price, 0);
};
export type { UserProps, Item };
❌ Bad:
export default UserProfile;
const UserProfile = () => <View />;
export { UserProfile as default };
Why: Named exports provide:
- Better IDE support (autocomplete, refactoring)
- Explicit imports (can't rename arbitrarily)
- Better tree-shaking
- No naming conflicts
Type-Only Imports
Use import type for type-only imports.
✅ Good:
import type { User } from '@data-access/graphql';
import type { ComponentProps } from './types';
import type { NativeStackScreenProps } from '@react-navigation/native-stack';
❌ Bad:
import { User } from '@data-access/graphql';
Why: Type-only imports are erased at compile time, reducing bundle size.
Path Aliases
Use configured path aliases instead of relative paths for cross-module imports.
Available aliases:
| Alias | Maps To | Usage |
|---|
@assets/* | src/assets/* | Static assets |
@data-access/* | src/data-access/* | API layer |
@entry-providers/* | src/entry-providers/* | Entry providers |
@features/* | src/features/* | UI features |
@libs/* | src/libs/* | Infrastructure |
@modules/* | src/modules/* | Modules |
@navigation/* | src/navigation/* | Navigation |
@operations/* | src/operations/* | Business logic |
@types/* | src/types/* | Shared types |
@zest/* | @zest/react-native | Design system |
✅ Good:
import { useT9n } from '@libs/localization';
import { RecipeCard } from '@features/recipe-card';
import { useGetRecipes } from '@data-access/query';
❌ Bad:
import { useT9n } from '../../../libs/localization';
import { RecipeCard } from '../../features/recipe-card';
Why: Path aliases remain stable during refactoring and are easier to read.
Restricted Imports
No Deep Imports
Import from public APIs, not internal modules.
✅ Good:
import { helper } from '@libs/localization';
import { parser } from '@data-access/query';
❌ Bad:
import { helper } from '@libs/localization/utils/helper';
import { parser } from '@data-access/query/helpers/parser';
Exceptions (one level allowed):
@libs/native-modules/*
@data-access/native/*
@data-access/query/*
@data-access/graphql/*
Why: Deep imports bypass public APIs and create tight coupling.
GraphQL gql Import
Always import gql from the data-access layer.
✅ Good:
import { gql } from '@data-access/graphql';
❌ Bad:
import { gql } from '@apollo/client';
Why: Centralizing graphql imports enables consistent configuration.
Grouping Related Imports
Group related imports from the same package on a single line.
✅ Good:
import { View, ScrollView, TouchableOpacity, FlatList } from 'react-native';
import { Button, Text, Input, useZestStyles } from '@zest/react-native';
import { useState, useEffect, useMemo, useCallback } from 'react';
❌ Bad:
import { View } from 'react-native';
import { ScrollView } from 'react-native';
import { TouchableOpacity } from 'react-native';
Why: Grouped imports reduce line count and clarify package dependencies.
Barrel Exports (index.ts)
Use barrel exports to create clean public APIs.
Module Barrel
export * from './stacks';
export * from './screens';
export * from './hooks';
export * from './stores';
export type * from './types';
Component Barrel
export { ProductCard } from './ProductCard';
export { LoadingProductCard } from './variants/loading';
export type { ProductCardProps, ProductVariant } from './types';
Screen Directory Barrel with Plop
export * from './cookbook-faq';
export * from './onboarding';
export * from './add-recipe-link-drawer';
Why: Barrel exports hide internal structure and enable simpler imports.
Inline Type Imports
Combine value and type imports when importing from the same source.
✅ Good:
import { View, type ImageStyle } from 'react-native';
import { useQuery, type UseQueryOptions } from '@tanstack/react-query';
❌ Bad:
import { View } from 'react-native';
import type { ImageStyle } from 'react-native';
Why: Inline type imports reduce line count while maintaining type-only semantics.
Platform-Specific Imports
Use platform-specific file extensions instead of conditional imports.
✅ Good:
import { useNavigation } from '@libs/navigation';
❌ Bad:
import { Platform } from 'react-native';
const useNavigation =
Platform.OS === 'ios'
? require('./useNavigation.ios').useNavigation
: require('./useNavigation.android').useNavigation;
Why: Build tools automatically select the correct file, eliminating runtime overhead.
Platform Extension Priority
Metro resolves files in this order:
{filename}.{platform}.{ext} (e.g., Button.ios.tsx)
{filename}.native.{ext} (React Native)
{filename}.rnsm.{ext} (RNSM modules)
{filename}.{ext} (fallback)
Testing Imports
Test files follow the same rules with testing libraries first.
import { render, screen, fireEvent } from '@testing-library/react-native';
import { QueryClientProvider } from '@tanstack/react-query';
import { queryClient } from '@libs/query';
import { SnapOnboardingScreen } from './SnapOnboardingScreen';
import { SNAP_ONBOARDING_SLIDES } from './constants';
import type { SnapOnboardingSlide } from './types';
Mock Imports
jest.mock('@react-navigation/native');
import { renderHook } from '@testing-library/react-native';
import { useNavigation } from './useNavigation';
Why: Jest requires mocks before imports. ESLint allows this exception.
Common Mistakes
❌ Don't Mix Import Styles
import React from 'react';
import { useState } from 'react';
const View = require('react-native').View;
import { useState, useEffect } from 'react';
import { View, Text } from 'react-native';
Why: Mixing styles prevents ESLint auto-fix and bypasses tree-shaking.
❌ Don't Skip Blank Lines
import { View } from 'react-native';
import { useQuery } from '@tanstack/react-query';
import { useT9n } from '@libs/localization';
import { View } from 'react-native';
import { useQuery } from '@tanstack/react-query';
import { useT9n } from '@libs/localization';
Why: Visual separation makes dependencies scannable.
❌ Don't Import Unused Dependencies
import { View, Text, ScrollView, FlatList } from 'react-native';
export const Component = () => (
<View>
<Text>Hello</Text>
</View>
);
import { View, Text } from 'react-native';
export const Component = () => (
<View>
<Text>Hello</Text>
</View>
);
Why: ESLint catches unused imports with @typescript-eslint/no-unused-vars.
❌ Don't Use Side Effect Imports
import './polyfills';
import { initializePolyfills } from './polyfills';
initializePolyfills();
Why: Side effect imports make dependencies unclear and harder to test.
Automatic Formatting
ESLint Auto-fix
yarn lint --fix
yarn typecheck
IDE Integration
VSCode (.vscode/settings.json):
{
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"editor.formatOnSave": true
}
Why: Automatic formatting prevents manual organization and reduces review friction.
ESLint Rules Reference
| Rule | Purpose |
|---|
import/order | Enforce import ordering with path groups |
import/no-default-export | Prevent default exports |
@typescript-eslint/consistent-type-imports | Use import type for types |
no-restricted-imports | Prevent deep imports |
@typescript-eslint/no-unused-vars | Remove unused imports |
Quick Reference
Import Order:
- React/React Native
- External libraries
- Internal aliases (@libs, @modules, @data-access, @zest)
- Parent/sibling imports
- Type imports (last)
Export Rules:
- ✅ Always use named exports
- ❌ Never use default exports
- ✅ Use
export type for types
- ✅ Use barrel exports (index.ts)
Commands:
yarn lint --fix
yarn typecheck
Additional Resources
For detailed examples and patterns, see: