| alwaysApply | true |
|---|
- Use functional components with hooks (no class components)
- Prefer named exports over default exports
- Keep components focused and single-responsibility
- Extract complex logic into custom hooks
Each component in its own directory under **/ComponentName:
ComponentName/
ComponentName.tsx # Main component
ComponentName.styles.ts # Styled-components styles (PascalCase)
ComponentName.types.ts # TypeScript interfaces
ComponentName.spec.tsx # Tests
ComponentName.constants.ts # Constants
ComponentName.story.tsx # Storybook examples
hooks/ # Custom hooks
components/ # Sub-components
utils/ # Utility functions
- Name as
ComponentNameProps - Use separate interfaces for complex prop objects
- Always use proper TypeScript types, never
any
- External dependencies (
react,redux, etc.) - Internal modules (aliases)
- Local imports (types, constants, hooks)
- Styles (always last:
import { Container } from './Component.styles')
Use barrel files (index.ts) only when exporting 3 or more items. Make sure exports appear in only one barrel file, not propagated up the chain.
We are migrating to styled-components (deprecating SCSS modules).
Keep all component styles in dedicated .styles.ts files using styled-components. Use PascalCase for the filename to match the component name:
ComponentName/
ComponentName.tsx
ComponentName.styles.ts # ✅ PascalCase
# Not component-name.styles.ts ❌
Keep all component styles in dedicated .style.ts files and import them with a namespace.
CRITICAL: import * as S is reserved for local styles only (e.g., from ComponentName.styles.ts). When you need to use styled components from external components, create a local styles file that re-exports them.
// ComponentName.tsx
import * as S from './ComponentName.styles'
return (
<S.Container>
<S.Title>Title</S.Title>
<S.Content>Content</S.Content>
</S.Container>
)
// ComponentName.styles.ts (when re-exporting from external component)
export { ExternalStyledComponent } from '../ExternalComponent/ExternalComponent.styles'// ❌ BAD: Importing styled components directly from external component
import * as S from '../ExternalComponent/ExternalComponent.styles'
// ❌ BAD: Named imports instead of namespace
import { Container, Title, Content } from './Component.styles'Prefer FlexGroup over div when creating flex containers:
// ✅ GOOD: Use FlexGroup
import { FlexGroup } from 'uiSrc/components/base/layout/flex'
export const Wrapper = styled(FlexGroup)`
user-select: none;
`
// Usage: Pass layout props as component props
<Wrapper align="center" justify="end">
{children}
</Wrapper>
// ❌ BAD: Using div with hardcoded flex properties
export const Wrapper = styled.div`
display: flex;
align-items: center;
justify-content: flex-end;
`Don't hardcode layout properties in styled components when using layout components like FlexGroup. Pass them as props instead:
// ✅ GOOD: Pass props in JSX
export const Wrapper = styled(FlexGroup)`
user-select: none;
`
<Wrapper align="center" justify="end">
{children}
</Wrapper>
// ❌ BAD: Hardcoding in styled component
export const Wrapper = styled(FlexGroup)`
align-items: center;
justify-content: flex-end;
user-select: none;
`Prefer gap prop on layout components instead of custom margins for spacing between elements:
// ✅ GOOD: Use gap prop
<Row align="center" justify="between" gap="l">
<FlexItem>Item 1</FlexItem>
<FlexItem>Item 2</FlexItem>
</Row>Always use theme spacing values instead of hardcoded pixel values:
// ✅ GOOD: Use theme spacing
export const Container = styled(Row)`
height: ${({ theme }) => theme.core.space.space500};
padding: 0 ${({ theme }) => theme.core.space.space200};
margin-bottom: ${({ theme }) => theme.core.space.space200};
`;
// ❌ BAD: Using magic numbers
export const Container = styled(Row)`
height: 64px;
padding: 0 16px;
margin-bottom: 16px;
`;Always use semantic colors from the theme instead of CSS variables or hardcoded colors:
// ✅ GOOD: Use semantic colors
export const Header = styled(Row)`
background-color: ${({ theme }) =>
theme.semantic.color.background.neutral100};
border-bottom: 1px solid
${({ theme }) => theme.semantic.color.border.neutral500};
`;
// ❌ BAD: Using deprecated EUI CSS variables
export const Header = styled(Row)`
background-color: var(--euiColorEmptyShade);
border-bottom: 1px solid var(--separatorColor);
`;Prefer layout components from the layout system instead of regular div elements:
// ✅ GOOD: Use Row component
import { Row } from 'uiSrc/components/base/layout/flex'
export const PageHeader = styled(Row)`
height: ${({ theme }) => theme.core.space.space500};
background-color: ${({ theme }) =>
theme.semantic.color.background.neutral100};
`
<PageHeader align="center" justify="between" gap="l">
{children}
</PageHeader>
// ❌ BAD: Using div with flex properties
export const PageHeader = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
height: 64px;
`Use $ prefix for transient props that shouldn't pass to DOM:
export const Button = styled.button<{ $isActive?: boolean }>`
background-color: ${({ $isActive }) => ($isActive ? '#007bff' : '#6c757d')};
`;Never use !important in styled-components. Styled-components handles CSS specificity through component hierarchy. If you need to override styles, use more specific selectors or adjust the component structure:
// ✅ GOOD: Rely on CSS specificity
export const IconButton = styled(IconButton)<{ isOpen: boolean }>`
${({ isOpen }) =>
isOpen &&
css`
background-color: ${({ theme }) =>
theme.semantic.color.background.primary200};
`}
`;
// ❌ BAD: Using !important
export const IconButton = styled(IconButton)`
background-color: ${({ theme }) =>
theme.semantic.color.background.primary200} !important;
`;When using layout components or other typed components, verify your prop values match the type system:
// Check the component's type definitions
// FlexGroup accepts: align?: 'center' | 'stretch' | 'baseline' | 'start' | 'end'
// Use valid values from the type system-
Global State (Redux):
- Data shared across multiple components
- Data persisting across routes
- Server state (API data)
- User preferences/settings
-
Local State (useState):
- UI state (modals, dropdowns, tabs)
- Form inputs before submission
- Component-specific temporary data
-
Derived State (Selectors):
- Computed values from Redux state
- Filtered/sorted lists
- Aggregated data
- Use
createSlicefrom Redux Toolkit - Define proper TypeScript types for state
- Use
PayloadAction<T>for action typing - Handle async with
extraReducersand thunks
- Use
createAsyncThunkfor async operations - Handle pending, fulfilled, and rejected states
- Use
rejectWithValuefor error handling
- Create basic selectors for direct state access
- Use
createSelectorfromreselectfor memoized/computed values - Keep selectors in separate
selectors.tsfile
- Use
useCallbackfor functions passed as props - Use
useMemofor expensive computations - Use
React.memofor expensive components - Avoid inline arrow functions in JSX props
Always clean up subscriptions, timers, and event listeners in useEffect return function.
- Use unique, stable IDs (not array indices)
- Only use indices if list never reorders and items have no IDs
- Use early returns for loading/error states
- Avoid deeply nested ternaries - extract to functions
Create custom hooks for reusable stateful logic. Store component-specific hooks in the component's /hooks directory.
Use Formik with Yup for validation. Keep form logic in custom hooks when complex.
- We are deprecating Elastic UI components
- Migrating to Redis UI (
@redis-ui/*) - Use internal wrappers from
uiSrc/components/ui - DO NOT import directly from
@redis-ui/*
// ✅ GOOD: Import from internal wrappers
import { Button, Input, FlexGroup } from 'uiSrc/components/ui';
// ❌ BAD: Don't import directly from @redis-ui
import { Button } from '@redis-ui/components';
// ❌ DEPRECATED: Don't use Elastic UI for new code
import { EuiButton } from '@elastic/eui';- ✅ Use internal wrappers from
uiSrc/components/uifor all new features - ✅ Create internal wrappers for Redis UI components as needed
- ✅ Replace Elastic UI when touching existing code
- ❌ Do not import directly from
@redis-ui/* - ❌ Do not add new Elastic UI imports
CRITICAL: Create a renderComponent helper function for each component test file:
describe('MyComponent', () => {
const defaultProps: MyComponentProps = {
id: faker.string.uuid(),
name: faker.person.fullName(),
onComplete: jest.fn(),
}
const renderComponent = (propsOverride?: Partial<MyComponentProps>) => {
const props = { ...defaultProps, ...propsOverride }
return render(
<Provider store={store}>
<MyComponent {...props} />
</Provider>
)
}
it('should render', () => {
renderComponent()
// assertions
})
})Benefits:
- Centralized setup and providers
- Default props in one place
- Easy prop overrides per test
- No duplicate render logic
Create a test store with configureStore for components connected to Redux.
- Separation of Concerns: Keep styles, types, constants, logic separate
- Colocate Related Code: Keep sub-components and hooks close to usage
- Consistent Naming: Follow conventions across all components
- Type Safety: Always define proper types, never
any - Testability: Structure for easy testing with
renderComponenthelper - Styled Components: Prefer styled-components over SCSS modules
- Layout Components: Use FlexGroup instead of div for flex containers, pass layout props as component props
- Type Safety: Verify prop values match component type definitions (e.g., FlexGroup's align/justify values)