| name | frontend |
| description | React/Redux frontend development patterns for RedisInsight UI: component folder structure, styled-components, hooks, named exports, barrel files, layout components, and theme usage. Use when editing any file under redisinsight/ui/**, writing or modifying React components, Redux slices, styled-components, custom hooks, or when the user mentions UI, frontend, React, Redux, or styled-components. |
Frontend Development (React/Redux)
Component Structure
Functional Components
- 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
Component Folder Structure
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
Props Interface
- Name as
ComponentNameProps
- Use separate interfaces for complex prop objects
- Always use proper TypeScript types, never
any
Imports Order in Components
- External dependencies (
react, redux, etc.)
- Internal modules (aliases)
- Local imports (types, constants, hooks)
- Styles (always last:
import { Container } from './Component.styles')
Barrel Files
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.
Styled Components
We are migrating to styled-components (deprecating SCSS modules).
Encapsulate Styles in .styles.ts
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 ❌
Import Pattern
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.
✅ Good
import * as S from './ComponentName.styles'
return (
<S.Container>
<S.Title>Title</S.Title>
<S.Content>Content</S.Content>
</S.Container>
)
export { ExternalStyledComponent } from '../ExternalComponent/ExternalComponent.styles'
❌ Bad
import * as S from '../ExternalComponent/ExternalComponent.styles'
import { Container, Title, Content } from './Component.styles'
Use Layout Components Instead of div
Prefer FlexGroup over div when creating flex containers:
import { FlexGroup } from 'uiSrc/components/base/layout/flex'
export const Wrapper = styled(FlexGroup)`
user-select: none;
`
<Wrapper align="center" justify="end">
{children}
</Wrapper>
export const Wrapper = styled.div`
display: flex;
align-items: center;
justify-content: flex-end;
`
Pass Layout Props as Component Props
Don't hardcode layout properties in styled components when using layout components like FlexGroup. Pass them as props instead:
export const Wrapper = styled(FlexGroup)`
user-select: none;
`
<Wrapper align="center" justify="end">
{children}
</Wrapper>
export const Wrapper = styled(FlexGroup)`
align-items: center;
justify-content: flex-end;
user-select: none;
`
Use Gap Prop Instead of Custom Margins
Prefer gap prop on layout components instead of custom margins for spacing between elements:
<Row align="center" justify="between" gap="l">
<FlexItem>Item 1</FlexItem>
<FlexItem>Item 2</FlexItem>
</Row>
Use Theme Spacing Instead of Magic Numbers
Always use theme spacing values instead of hardcoded pixel values:
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};
`;
export const Container = styled(Row)`
height: 64px;
padding: 0 16px;
margin-bottom: 16px;
`;
Use Semantic Colors from Theme
Always use semantic colors from the theme instead of CSS variables or hardcoded colors:
export const Header = styled(Row)`
background-color: ${({ theme }) =>
theme.semantic.color.background.neutral100};
border-bottom: 1px solid
${({ theme }) => theme.semantic.color.border.neutral500};
`;
export const Header = styled(Row)`
background-color: var(--euiColorEmptyShade);
border-bottom: 1px solid var(--separatorColor);
`;
Use Layout Components (Row/Col/FlexGroup) Instead of div
Prefer layout components from the layout system instead of regular div elements:
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>
export const PageHeader = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
height: 64px;
`
Conditional Styling
Use $ prefix for transient props that shouldn't pass to DOM:
export const Button = styled.button<{ $isActive?: boolean }>`
background-color: ${({ $isActive }) => ($isActive ? '#007bff' : '#6c757d')};
`;
Avoid !important
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:
export const IconButton = styled(IconButton)<{ isOpen: boolean }>`
${({ isOpen }) =>
isOpen &&
css`
background-color: ${({ theme }) =>
theme.semantic.color.background.primary200};
`}
`;
export const IconButton = styled(IconButton)`
background-color: ${({ theme }) =>
theme.semantic.color.background.primary200} !important;
`;
Verify Type System Compatibility
When using layout components or other typed components, verify your prop values match the type system:
State Management (Redux)
When to Use What
Redux Toolkit Patterns
Slice Structure
- Use
createSlice from Redux Toolkit
- Define proper TypeScript types for state
- Use
PayloadAction<T> for action typing
- Handle async with
extraReducers and thunks
Thunks
- Use
createAsyncThunk for async operations
- Handle pending, fulfilled, and rejected states
- Use
rejectWithValue for error handling
Selectors
- Create basic selectors for direct state access
- Use
createSelector from reselect for memoized/computed values
- Keep selectors in separate
selectors.ts file
React Best Practices
Performance
- Use
useCallback for functions passed as props
- Use
useMemo for expensive computations
- Use
React.memo for expensive components
- Avoid inline arrow functions in JSX props
Effect Cleanup
Always clean up subscriptions, timers, and event listeners in useEffect return function.
Keys in Lists
- Use unique, stable IDs (not array indices)
- Only use indices if list never reorders and items have no IDs
Conditional Rendering
- Use early returns for loading/error states
- Avoid deeply nested ternaries - extract to functions
Custom Hooks
Extract Reusable Logic
Create custom hooks for reusable stateful logic. Store component-specific hooks in the component's /hooks directory.
Form Handling
Use Formik with Yup for validation. Keep form logic in custom hooks when complex.
UI Components
⚠️ IMPORTANT:
- 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/*
Component Usage
import { Button, Input, FlexGroup } from 'uiSrc/components/ui';
import { Button } from '@redis-ui/components';
import { EuiButton } from '@elastic/eui';
Migration Guidelines
- ✅ Use internal wrappers from
uiSrc/components/ui for 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
Icons
⚠️ IMPORTANT: Always use icons from Redis UI library instead of custom SVGs.
Icon Usage
- Use Redis UI icons from
@redis-ui/icons via iconRegistry.tsx
- Icons are automatically exported via
export * from '@redis-ui/icons' in iconRegistry.tsx
- Use
RiIcon component with icon type: <RiIcon type="FolderOpenIcon" />
- DO NOT create custom SVG icons - check if the icon exists in Redis UI library first
Custom Icons (Exception)
Only create custom SVG icons if:
- The icon doesn't exist in Redis UI library
- It's a project-specific icon that won't be added to the library
Testing Components
Always Use Shared renderComponent Helper
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()
})
})
Benefits:
- Centralized setup and providers
- Default props in one place
- Easy prop overrides per test
- No duplicate render logic
Testing Redux
Create a test store with configureStore for components connected to Redux.
Key Principles
- 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
renderComponent helper
- 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)