| name | constants |
| description | WHAT: Constants organization with naming conventions and type-safe as const assertions. WHEN: defining test IDs, animation durations, query keys, feature flags. KEYWORDS: constants, SCREAMING_SNAKE_CASE, PascalCase, as const, test IDs, query keys, type safety, naming. |
Constants Organization Patterns
Core Principles
Organize constants in feature-level constants.ts files. Use SCREAMING_SNAKE_CASE for primitive constants, PascalCase for grouped constant objects, and always add as const for type safety and literal types.
Why: Centralized constants prevent magic numbers, improve maintainability, enable easy updates, and provide type-safe access to configuration values.
When to Use This Skill
Use these patterns when:
- Defining animation durations or timing values
- Organizing test IDs for E2E testing
- Setting up analytics event destinations
- Declaring UI sizes, spacing, or limits
- Managing feature flag keys
- Defining query keys for data access
- Creating configuration constants
- Avoiding magic numbers in code
File Organization
Feature-Level Constants
Create constants.ts at feature root.
features/
└── reactivation-banner-feature/
├── components/
├── hooks/
├── constants.ts # Feature constants
└── index.ts
Why: Feature-level organization keeps constants close to their usage, making them easier to find and maintain.
Production Example: git-resources/shared-mobile-modules/src/features/reactivation-banner-feature/constants.ts:1
Module/Library-Level Constants
For shared constants across multiple features:
libs/
└── tracing/
├── hooks/
├── utils/
├── constants.ts # Shared tracing constants
└── index.ts
data-access/
└── native/
├── auth/
│ └── constants.ts # Auth repository constants
├── plan/
│ └── constants.ts # Plan repository constants
└── constants.ts # Aggregated repository keys
Why: Centralized constants at module level enable reuse across features while maintaining clear organization.
Production Example: git-resources/shared-mobile-modules/src/data-access/native/constants.ts:1
Naming Conventions
SCREAMING_SNAKE_CASE for Primitives
Use SCREAMING_SNAKE_CASE for primitive constants.
export const BANNER_ANIMATION_DURATION = 250;
export const API_TIMEOUT_MS = 5000;
export const DEFAULT_LOCALE = 'en-US';
export const MAX_RETRY_ATTEMPTS = 3;
export const maxRetries = 3;
export const API-TIMEOUT = 5000;
export const defaultlocale = 'en-US';
Why: SCREAMING_SNAKE_CASE clearly identifies constants at a glance and distinguishes them from variables.
PascalCase for Constant Objects
Use PascalCase for grouped constant objects.
export const TestIds = {
BUTTON: 'submit-button',
INPUT: 'email-input',
MODAL: 'confirmation-modal',
} as const;
export const AnimationDurations = {
FAST: 150,
NORMAL: 300,
SLOW: 500,
} as const;
export const VitalAttributesKeys = {
COUNTRY: 'application.real_country',
LOCALE: 'application.locale',
CUSTOMER_ID: 'application.customer.id',
} as const;
export const test_ids = { ... };
export const ANIMATION_DURATIONS = { ... };
Why: PascalCase for objects differentiates them from primitive constants and follows TypeScript conventions.
Production Example: git-resources/shared-mobile-modules/src/libs/tracing/constants.ts:5
Type-Safe Constants with as const
Always Use as const
Add as const assertion for readonly literal types.
export const TEST_IDS = {
BUTTON: 'submit-button',
INPUT: 'email-input',
} as const;
export const TEST_IDS = {
BUTTON: 'submit-button',
INPUT: 'email-input',
};
Why: as const provides:
- Literal types instead of widened types
- Readonly properties (prevents accidental modification)
- Type-safe access to constant values
- Better TypeScript inference
Extract Type from Constant Object
Create type aliases from constant objects for type-safe usage.
export const RecipeCategories = {
BREAKFAST: 'breakfast',
LUNCH: 'lunch',
DINNER: 'dinner',
} as const;
export type RecipeCategory = (typeof RecipeCategories)[keyof typeof RecipeCategories];
const getRecipesByCategory = (category: RecipeCategory) => {
};
getRecipesByCategory(RecipeCategories.BREAKFAST);
getRecipesByCategory('breakfast');
getRecipesByCategory('snack');
Why: Extracting types from constants ensures single source of truth and prevents type/value drift.
Production Example: git-resources/shared-mobile-modules/src/libs/tracing/constants.ts:13
Const Objects vs Enums
Prefer Const Objects Over Enums
Use const objects instead of TypeScript enums.
export const RecipeCategories = {
BREAKFAST: 'breakfast',
LUNCH: 'lunch',
DINNER: 'dinner',
} as const;
export type RecipeCategory = (typeof RecipeCategories)[keyof typeof RecipeCategories];
const category = RecipeCategories.BREAKFAST;
enum RecipeCategory {
BREAKFAST = 'breakfast',
LUNCH = 'lunch',
DINNER = 'dinner',
}
Why: Const objects have:
- Better TypeScript support
- Smaller bundle size (no generated code)
- More predictable transpilation
- Easier debugging (values are literal strings)
Constant Types by Use Case
Animation Constants
Define animation durations and configurations.
export const BANNER_ANIMATION_DURATION = 250;
export const BANNER_FADE_ANIMATION_DURATION = 500;
export const BANNER_SLIDE_DISTANCE = 100;
export const BannerAnimation = {
DURATION: 250,
FADE_DURATION: 500,
SLIDE_DISTANCE: 100,
} as const;
Why: Consistent animation timing improves UX and makes global changes easy (e.g., accessibility preferences for reduced motion).
Production Example: git-resources/shared-mobile-modules/src/features/reactivation-banner-feature/constants.ts:1
Test IDs
Group test IDs by feature for E2E testing.
export const TEST_IDS = {
EXPANDED_BANNER: 'reactivation-banner-expanded',
EXPANDED_TITLE: 'reactivation-banner-expanded-title',
EXPANDED_DESCRIPTION: 'reactivation-banner-expanded-description',
PLAN_DETAILS_BUTTON: 'reactivation-banner-plan-details-button',
REVIEW_PLAN_BUTTON: 'reactivation-banner-review-plan-button',
PROMO_CODE_MODAL: 'promo-code-modal',
PROMO_CODE_INPUT: 'promo-code-modal-input',
PROMO_CODE_UPDATE_BUTTON: 'promo-code-modal-update-button',
DISCOUNT_ERROR_DIALOG: 'discount-error-dialog',
DISCOUNT_ERROR_DIALOG_ICON: 'discount-error-dialog-icon',
} as const;
<View testID={TEST_IDS.EXPANDED_BANNER}>
<Text testID={TEST_IDS.EXPANDED_TITLE}>{title}</Text>
<Button testID={TEST_IDS.REVIEW_PLAN_BUTTON} />
</View>
expect(screen.getByTestId(TEST_IDS.EXPANDED_BANNER)).toBeTruthy();
Why: Grouped test IDs prevent duplication, enable easy lookup, and ensure consistency between implementation and tests.
Production Example: git-resources/shared-mobile-modules/src/features/reactivation-banner-feature/constants.ts:4
Analytics Constants
Define analytics destinations and default parameter keys.
import type { DefaultAnalyticsParams } from './types';
export const DEFAULT_ANALYTICS_KEYS: readonly (keyof DefaultAnalyticsParams)[] =
[
'eventName',
'eventCategory',
'eventAction',
'eventLabel',
'screenName',
'tribe',
] as const;
export const getValidDefaultAnalyticsKeys = <
T extends Partial<DefaultAnalyticsParams>,
>(
params: T
): Array<keyof DefaultAnalyticsParams> => {
return DEFAULT_ANALYTICS_KEYS.filter(
(key) => key in params && params[key] != null
);
};
Why: Centralized analytics constants ensure consistency across events and enable compile-time validation of parameter keys.
Production Example: git-resources/shared-mobile-modules/src/libs/analytics/constants.ts:11
UI Constants
Define spacing, sizes, and limits.
export const UI_CONSTANTS = {
MAX_RECIPE_NAME_LENGTH: 100,
DEFAULT_PAGE_SIZE: 20,
MIN_SEARCH_QUERY_LENGTH: 3,
CARD_BORDER_RADIUS: 8,
HEADER_HEIGHT: 64,
} as const;
export const ToastConfig = {
DEFAULT_DURATION: 5000,
FADE_DURATION: 300,
ICON_CONFIG: {
success: {
icon: 'CircleCheckmarkOutline24' as const,
color: 'alias.color.positive.background.default' as const,
},
error: {
icon: 'CircleMinusOutline24' as const,
color: 'alias.color.negative.background.default' as const,
},
},
} as const;
Why: UI constants enable consistent sizing, prevent magic numbers, and centralize design system values.
Production Example: git-resources/shared-mobile-modules/src/features/toast-feature/constants.ts:5
Query Keys for Data Access
Define query keys in data access layer.
export const PLAN_QUERY_KEY = 'plan';
export const AUTH_QUERY_KEY = 'auth';
export const NATIVE_MODULES_REPOSITORY_QUERY_KEY = 'nativeRepositories';
export const REPOSITORY_KEYS = {
auth: AUTH_QUERY_KEY,
appConfig: APP_CONFIG_QUERY_KEY,
plan: PLAN_QUERY_KEY,
navigationBar: NAVIGATION_BAR_QUERY_KEY,
} as const;
export type RepositoryName = keyof typeof REPOSITORY_KEYS;
Why: Centralized query keys prevent cache key conflicts and enable type-safe repository access.
Production Example: git-resources/shared-mobile-modules/src/data-access/native/constants.ts:30
Feature Flag Keys
Organize feature flags by module using enums (exception to const object rule).
export enum OnboardingModuleFeatureFlagKeys {
WELCOME_CAROUSEL_V2_FEATURE_FLAG = 'rnsm_onboarding_welcome_carousel_v2',
}
export enum StoreModuleFeatureFlagKeys {
RNSM_STOREFRONT_DESELECT_MEALS_CTA = 'rnsm_storefront_deselect_meals_cta',
RNSM_SEAMLESS_BOX_DOWNGRADE = 'rn_seamless_box_downgrade',
NEW_APP_ONBOARDING = 'new_app_onboarding',
}
export enum LoyaltyProgramModuleFeatureFlagKeys {
LOYALTY_PROGRAM = 'loyalty_program',
SMOOTHIE_BOX_CHALLENGE = 'smoothie_box_challenge',
}
type EnumValues<E extends Record<string, string>> = `${E[keyof E]}`;
export type FeatureFlagKeys =
| EnumValues<typeof OnboardingModuleFeatureFlagKeys>
| EnumValues<typeof StoreModuleFeatureFlagKeys>
| EnumValues<typeof LoyaltyProgramModuleFeatureFlagKeys>;
Why: Enums provide module-based organization for feature flags. This is an exception to the "prefer const objects" rule because feature flags need module-level grouping and exhaustive checking.
Production Example: git-resources/shared-mobile-modules/src/libs/native-modules/feature-toggle/constants/featureFlagKeys.ts:16
Tracing Constants
Define span keys and vital attribute keys for OpenTelemetry.
export const VITAL_ATTRIBUTES_KEYS = {
SYSTEM_COUNTRY: 'application.system_country',
COUNTRY: 'application.real_country',
LOCALE: 'application.locale',
CUSTOMER_UUID: 'application.customer.uuid',
CUSTOMER_ID: 'application.customer.id',
} as const;
export type VitalAttributeKey =
(typeof VITAL_ATTRIBUTES_KEYS)[keyof typeof VITAL_ATTRIBUTES_KEYS];
span.setAttribute(
VITAL_ATTRIBUTES_KEYS.COUNTRY,
user.country
);
Why: Standardized attribute keys ensure consistency across all traces and follow OpenTelemetry conventions.
Production Example: git-resources/shared-mobile-modules/src/libs/tracing/constants.ts:5
Common Patterns
Combine Related Constants
Group related constants in objects.
export const BannerConfig = {
ANIMATION_DURATION: 250,
MAX_HEIGHT: 200,
PADDING: 16,
TEST_IDS: {
CONTAINER: 'banner-container',
CLOSE_BUTTON: 'banner-close',
TITLE: 'banner-title',
},
} as const;
const { ANIMATION_DURATION, TEST_IDS } = BannerConfig;
Why: Grouping prevents constant proliferation and improves organization by keeping related values together.
Export Individual and Grouped
Export both individual values and grouped objects for flexibility.
export const ANIMATION_DURATION = 250;
export const MAX_HEIGHT = 200;
export const PADDING = 16;
export const BannerConfig = {
ANIMATION_DURATION,
MAX_HEIGHT,
PADDING,
} as const;
import { ANIMATION_DURATION } from './constants';
import { BannerConfig } from './constants';
setTimeout(doSomething, ANIMATION_DURATION);
setTimeout(doSomething, BannerConfig.ANIMATION_DURATION);
Why: Flexible exports support different usage patterns - direct access for frequently used values, namespaced access for clarity.
Environment-Specific Constants
Define environment-specific values with runtime checks.
export const API_BASE_URL =
process.env.NODE_ENV === 'production'
? 'https://api.yourcompany.com'
: 'https://api-staging.yourcompany.com';
export const API_TIMEOUT_MS = 30000;
export const MAX_RETRY_ATTEMPTS = 3;
export const FEATURE_DEFAULTS = {
enableBetaFeatures: process.env.NODE_ENV !== 'production',
enableDebugLogging: __DEV__,
} as const;
Why: Environment constants enable easy configuration changes and clear separation of production vs development behavior.
Documentation
Document Purpose with JSDoc
Add comments explaining constant purpose, especially for non-obvious values.
export const MAX_RETRY_ATTEMPTS = 3;
export const BANNER_ANIMATION_DURATION = 250;
export const VITAL_ATTRIBUTES_KEYS = {
COUNTRY: 'application.real_country',
LOCALE: 'application.locale',
CUSTOMER_ID: 'application.customer.id',
} as const;
Why: Documentation helps developers understand when and how to use constants, provides context for magic numbers, and links to relevant specifications.
Testing with Constants
Use Constants in Tests
Import and use constants in tests to ensure consistency.
import { TEST_IDS, ANIMATION_DURATION, BannerConfig } from './constants';
test('renders with correct testID', () => {
render(<Banner />);
expect(screen.getByTestId(TEST_IDS.EXPANDED_BANNER)).toBeTruthy();
});
test('animation completes within duration', async () => {
jest.useFakeTimers();
render(<Banner />);
act(() => {
jest.advanceTimersByTime(ANIMATION_DURATION);
});
expect(screen.getByTestId(TEST_IDS.EXPANDED_BANNER)).toHaveStyle({
opacity: 1,
});
});
test('banner respects max height', () => {
render(<Banner />);
const banner = screen.getByTestId(TEST_IDS.EXPANDED_BANNER);
expect(banner.props.style.maxHeight).toBe(BannerConfig.MAX_HEIGHT);
});
Why: Using constants in tests ensures consistency with implementation and makes refactoring easier (change constant value in one place).
Common Mistakes to Avoid
❌ Don't use magic numbers:
setTimeout(() => doSomething(), 250);
if (items.length > 20) {
loadMore();
}
✅ Do use descriptive constants:
export const BANNER_ANIMATION_DURATION_MS = 250;
export const MAX_ITEMS_PER_PAGE = 20;
setTimeout(() => doSomething(), BANNER_ANIMATION_DURATION_MS);
if (items.length > MAX_ITEMS_PER_PAGE) {
loadMore();
}
❌ Don't forget as const:
export const TEST_IDS = {
BUTTON: 'button',
};
export const RecipeCategories = {
BREAKFAST: 'breakfast',
LUNCH: 'lunch',
};
✅ Do use as const for literal types:
export const TEST_IDS = {
BUTTON: 'button',
} as const;
export const RecipeCategories = {
BREAKFAST: 'breakfast',
LUNCH: 'lunch',
} as const;
❌ Don't use inconsistent casing:
export const maxRetries = 3;
export const API_TIMEOUT = 5000;
export const DefaultLocale = 'en';
export const test_ids = { ... };
✅ Do use consistent conventions:
export const MAX_RETRIES = 3;
export const API_TIMEOUT_MS = 5000;
export const DEFAULT_LOCALE = 'en-US';
export const TestIds = { ... } as const;
export const ApiConfig = { ... } as const;
❌ Don't duplicate constants:
export const ANIMATION_DURATION = 250;
export const ANIMATION_DURATION = 250;
export const ANIMATION_DURATION = 250;
✅ Do centralize shared constants:
export const AnimationDurations = {
FAST: 150,
NORMAL: 250,
SLOW: 500,
} as const;
import { AnimationDurations } from '@libs/animation/constants';
const duration = AnimationDurations.NORMAL;
❌ Don't hardcode test IDs in multiple places:
<View testID="reactivation-banner-expanded">
{}
</View>
expect(screen.getByTestId('reactivation-banner-expanded')).toBeTruthy();
✅ Do use constant test IDs:
export const TEST_IDS = {
EXPANDED_BANNER: 'reactivation-banner-expanded',
} as const;
<View testID={TEST_IDS.EXPANDED_BANNER}>
{/* ... */}
</View>
expect(screen.getByTestId(TEST_IDS.EXPANDED_BANNER)).toBeTruthy();
Quick Reference
Feature-level constants:
export const ANIMATION_DURATION = 250;
export const MAX_HEIGHT = 200;
export const TEST_IDS = {
CONTAINER: 'my-feature-container',
BUTTON: 'my-feature-button',
} as const;
Type-safe constant objects:
export const RecipeCategories = {
BREAKFAST: 'breakfast',
LUNCH: 'lunch',
DINNER: 'dinner',
} as const;
export type RecipeCategory = (typeof RecipeCategories)[keyof typeof RecipeCategories];
Grouped configuration:
export const ApiConfig = {
BASE_URL: 'https://api.example.com',
TIMEOUT_MS: 5000,
MAX_RETRIES: 3,
} as const;
Feature flags (enum exception):
export enum MyModuleFeatureFlagKeys {
NEW_UI = 'my_module_new_ui',
BETA_FEATURE = 'my_module_beta',
}
Naming conventions:
- Primitives:
SCREAMING_SNAKE_CASE
- Objects:
PascalCase
- Always use
as const for objects
- Prefer const objects over enums (except feature flags)
Key Libraries:
- React Native 0.75.4
- TypeScript 5.1.6
For production examples, see references/examples.md.