# Frontend

> 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.

- Skill: `redis/frontend` (Agent Skill)
- Install (CLI): `npx skillmds@latest add redis/frontend`
- Raw SKILL.md: https://api.skillmd.com/api/skills/redis/frontend/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: redis (https://skillmd.com/u/redis)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/redis/frontend

---


# 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

1. External dependencies (`react`, `redux`, etc.)
2. Internal modules (aliases)
3. Local imports (types, constants, hooks)
4. 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

```typescript
// 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

```typescript
// ❌ 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';
```

### Use Layout Components Instead of div

**Prefer `FlexGroup` over `div`** when creating flex containers:

```typescript
// ✅ 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;
`
```

### 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:

```typescript
// ✅ 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;
`
```

### Use Gap Prop Instead of Custom Margins

**Prefer `gap` prop on layout components** instead of custom margins for spacing between elements:

```typescript
// ✅ GOOD: Use gap prop
<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:

```typescript
// ✅ 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;
`;
```

### Use Semantic Colors from Theme

**Always use semantic colors** from the theme instead of CSS variables or hardcoded colors:

```typescript
// ✅ 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);
`;
```

### Use Layout Components (Row/Col/FlexGroup) Instead of div

**Prefer layout components** from the layout system instead of regular `div` elements:

```typescript
// ✅ 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;
`
```

### Conditional Styling

Use `$` prefix for transient props that shouldn't pass to DOM:

```typescript
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:

```typescript
// ✅ 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;
`;
```

### Verify Type System Compatibility

When using layout components or other typed components, verify your prop values match the type system:

```typescript
// Check the component's type definitions
// FlexGroup accepts: align?: 'center' | 'stretch' | 'baseline' | 'start' | 'end'
// Use valid values from the type system
```

## State Management (Redux)

### When to Use What

- **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

### 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

```typescript
// ✅ 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';
```

### 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:

Pull entity data from a shared Fishery factory in `redisinsight/ui/src/mocks/factories/`
(reuse one, or add a new `<domain>/<TypeName>.factory.ts`); inline only callbacks/primitives.

```typescript
describe('MyComponent', () => {
  const mockUser = UserFactory.build();
  const defaultProps: MyComponentProps = {
    user: mockUser,
    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

### Testing Redux

Create a test store with `configureStore` for components connected to Redux.

## Key Principles

1. **Separation of Concerns**: Keep styles, types, constants, logic separate
2. **Colocate Related Code**: Keep sub-components and hooks close to usage
3. **Consistent Naming**: Follow conventions across all components
4. **Type Safety**: Always define proper types, never `any`
5. **Testability**: Structure for easy testing with `renderComponent` helper
6. **Styled Components**: Prefer styled-components over SCSS modules
7. **Layout Components**: Use FlexGroup instead of div for flex containers, pass layout props as component props
8. **Type Safety**: Verify prop values match component type definitions (e.g., FlexGroup's align/justify values)

