Skip to content

Latest commit

 

History

History
1315 lines (1035 loc) · 29.4 KB

File metadata and controls

1315 lines (1035 loc) · 29.4 KB

Code Conventions

Table of Contents

  1. Introduction
  2. Code Structure
  3. Naming Conventions
  4. TypeScript Usage
  5. React Component Patterns
  6. Hooks Conventions
  7. Error Handling
  8. State Management
  9. Styling Conventions
  10. Memory Management
  11. Atomic Design Specific Rules

Introduction

This document outlines the coding conventions and best practices for the React Native Atomic Design project. Following these conventions ensures code consistency, maintainability, and scalability across the codebase.

Purpose:

  • Establish consistent coding standards
  • Improve code readability and maintainability
  • Facilitate team collaboration
  • Ensure type safety and error prevention
  • Guide component creation and organization

All code in this project should adhere to these conventions. When in doubt, refer to this document or discuss with the team.

Code Structure

Atomic Design Folder Structure

The project follows a strict Atomic Design folder structure:

src/
├── views/
│   ├── atoms/              # Basic building blocks
│   ├── components/         # Composed components (molecules)
│   └── containers/         # Screen containers (organisms)
├── hooks/                  # Custom React hooks
├── services/               # Business logic services
├── entities/               # Domain entities and DTOs
├── managers/               # Manager classes
├── types/                  # TypeScript type definitions
└── config/                 # Configuration files

Component File Structure

Atoms and Components

Each atom or component should be in its own file:

atoms/
  └── Card.tsx              # Single component per file

Containers

Each container follows a consistent multi-file structure:

containers/
  └── home/
      ├── home.screen.tsx   # Logic and state management
      ├── home.view.tsx     # Presentation component (with styled-components)
      ├── home.props.ts     # Props interfaces
      └── home.types.ts     # TypeScript types

File Naming:

  • Use kebab-case for container folders: home/, profile/, settings/
  • Use dot notation for container files: home.screen.tsx, home.view.tsx
  • Use PascalCase for component files: Card.tsx, RepositoryCard.tsx

Barrel Exports (index.ts files)

While not currently implemented, barrel exports can be used to simplify imports:

// atoms/index.ts
export {Card} from './Card';
export {LoadingSpinner} from './LoadingSpinner';
export {ErrorMessage} from './ErrorMessage';

// Usage
import {Card, LoadingSpinner} from '../atoms';

Guidelines:

  • Use barrel exports for frequently imported components
  • Keep barrel exports simple and focused
  • Don't create deep barrel export chains

Separation of Concerns

Component Responsibilities

Atoms:

  • Render a single UI element
  • Accept props for customization
  • No business logic
  • No state management (except local UI state)

Components (Molecules):

  • Combine multiple atoms
  • Simple presentation logic only
  • No API calls or data fetching
  • No complex state management

Containers (Organisms):

  • Manage business logic and state
  • Coordinate data fetching via hooks
  • Separate logic (screen.tsx) from presentation (view.tsx)
  • Handle user interactions and side effects

Hooks:

  • Encapsulate data fetching logic
  • Manage loading and error states
  • Provide clean API for components

Services:

  • Handle API communication
  • Transform DTOs to domain models
  • Isolate external dependencies

Naming Conventions

Variables and Functions

Use camelCase for variables and functions:

// Good
const repositoryList = [];
const fetchRepositories = async () => {};

// Bad
const RepositoryList = [];
const FetchRepositories = async () => {};

React Components

Use PascalCase for React components:

// Good
export const Card: React.FC<CardProps> = ({children}) => {};
export const RepositoryCard: React.FC<RepositoryCardProps> = ({repository}) => {};

// Bad
export const card: React.FC<CardProps> = ({children}) => {};
export const repositoryCard: React.FC<RepositoryCardProps> = ({repository}) => {};

Constants

Use UPPER_SNAKE_CASE for constants:

// Good
const API_BASE_URL = 'https://api.github.com';
const MAX_RETRY_ATTEMPTS = 3;
const DEFAULT_PAGE_SIZE = 10;

// Bad
const apiBaseUrl = 'https://api.github.com';
const maxRetryAttempts = 3;

TypeScript Interfaces and Types

Use PascalCase for interfaces and types:

// Good
interface CardProps {
  children: React.ReactNode;
}

type RepositoryStatus = 'loading' | 'success' | 'error';

// Bad
interface cardProps {
  children: React.ReactNode;
}

File Naming

  • Components: PascalCase - Card.tsx, RepositoryCard.tsx
  • Containers: kebab-case folders, dot notation files - home/home.screen.tsx
  • Hooks: camelCase with use prefix - useRepositories.ts
  • Services: camelCase with .service.ts suffix - api.service.ts
  • Types: camelCase with .types.ts suffix - repository.types.ts
  • Utils: camelCase - formatDate.ts, validateEmail.ts

Atomic Design Level Naming Patterns

Atoms:

  • Simple, descriptive names: Card, Button, Input, Label
  • No feature-specific prefixes

Components (Molecules):

  • Descriptive, feature-specific: RepositoryCard, SearchBar, FormField
  • Indicates what it displays or does

Containers (Organisms):

  • Screen or feature name: HomeScreen, ProfileScreen, SettingsScreen
  • Followed by .screen.tsx and .view.tsx files

TypeScript Usage

Type Definitions

Always define types for component props, function parameters, and return values:

// Good
interface CardProps {
  children: React.ReactNode;
  style?: ViewStyle;
}

export const Card: React.FC<CardProps> = ({children, style}) => {
  // Implementation
};

// Bad
export const Card = ({children, style}) => {
  // No type safety
};

Interface vs Type

Use Interfaces for:

  • Component props
  • Object shapes that may be extended
  • Public APIs
interface CardProps {
  children: React.ReactNode;
  style?: ViewStyle;
}

interface RepositoryCardProps extends CardProps {
  repository: GithubRepo;
}

Use Types for:

  • Unions and intersections
  • Primitives and computed types
  • Type aliases
type Status = 'loading' | 'success' | 'error';
type Nullable<T> = T | null;
type RepositoryWithStatus = GithubRepo & {status: Status};

Props Interfaces

Always define props interfaces for components:

// Good - Separate props file for containers
// home.props.ts
export interface HomeViewProps {
  repositories: GithubRepo[];
  loading: boolean;
  error: Error | null;
}

// home.view.tsx
import {HomeViewProps} from './home.props';

export const HomeView: React.FC<HomeViewProps> = ({
  repositories,
  loading,
  error,
}) => {
  // Implementation
};

// Good - Inline for simple components
interface CardProps {
  children: React.ReactNode;
  style?: ViewStyle;
}

Generic Types

Use generics for reusable components and utilities:

// Good
interface BaseResponse<T> {
  items: T;
  total: number;
}

interface ApiResponse<T> {
  data: T;
  status: number;
  message?: string;
}

// Usage
type RepositoryResponse = BaseResponse<GithubRepo>;

Type Assertions

Avoid type assertions when possible. Use type guards instead:

// Good - Type guard
function isError(value: unknown): value is Error {
  return value instanceof Error;
}

if (isError(error)) {
  console.error(error.message);
}

// Avoid - Type assertion
const error = someValue as Error;

React Component Patterns

Functional Components with Hooks

Always use functional components with hooks:

// Good
export const Card: React.FC<CardProps> = ({children, style}) => {
  return <View style={[styles.card, style]}>{children}</View>;
};

// Bad - Class components are deprecated
export class Card extends React.Component<CardProps> {
  render() {
    return <View>{this.props.children}</View>;
  }
}

Component Composition

Build complex components by composing simpler ones:

// Good - Composition
export const RepositoryCard: React.FC<RepositoryCardProps> = ({repository}) => {
  return (
    <Card style={styles.container}>
      <Text style={styles.name}>{repository.name}</Text>
      <Text style={styles.description}>{repository.description}</Text>
    </Card>
  );
};

// Bad - Monolithic component
export const RepositoryCard: React.FC<RepositoryCardProps> = ({repository}) => {
  return (
    <View style={[styles.card, styles.container]}>
      {/* Duplicated card styling */}
    </View>
  );
};

Props Destructuring

Always destructure props in the function signature:

// Good
export const Card: React.FC<CardProps> = ({children, style}) => {
  return <View style={[styles.card, style]}>{children}</View>;
};

// Acceptable for many props
export const RepositoryCard: React.FC<RepositoryCardProps> = (props) => {
  const {repository} = props;
  // Implementation
};

// Bad
export const Card: React.FC<CardProps> = (props) => {
  return <View style={[styles.card, props.style]}>{props.children}</View>;
};

Default Props

Provide default values for optional props:

// Good - Default parameter
export const LoadingSpinner: React.FC<LoadingSpinnerProps> = ({
  message = 'Loading...',
}) => {
  return (
    <View style={styles.container}>
      <ActivityIndicator size="large" color="#007AFF" />
      <Text style={styles.text}>{message}</Text>
    </View>
  );
};

// Or use defaultProps (less common with functional components)
LoadingSpinner.defaultProps = {
  message: 'Loading...',
};

Component Documentation

Add JSDoc comments for complex components:

/**
 * Card Atom
 * Basic card container component with shadow and border radius
 * 
 * @param children - Content to display inside the card
 * @param style - Optional style overrides
 */
export const Card: React.FC<CardProps> = ({children, style}) => {
  return <View style={[styles.card, style]}>{children}</View>;
};

Hooks Conventions

useState Usage

Use descriptive state variable names and initialize with proper types:

// Good
const [repositories, setRepositories] = useState<GithubRepo[]>([]);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<Error | null>(null);

// Bad
const [data, setData] = useState([]);
const [isLoading, setIsLoading] = useState(false);

useEffect Patterns and Cleanup

Always include cleanup in useEffect when needed:

// Good - With cleanup
useEffect(() => {
  const subscription = someObservable.subscribe(handleUpdate);
  
  return () => {
    subscription.unsubscribe();
  };
}, []);

// Good - Async operations
useEffect(() => {
  let cancelled = false;
  
  const fetchData = async () => {
    const data = await api.fetchData();
    if (!cancelled) {
      setData(data);
    }
  };
  
  fetchData();
  
  return () => {
    cancelled = true;
  };
}, []);

// Bad - No cleanup
useEffect(() => {
  someObservable.subscribe(handleUpdate);
  // Memory leak!
}, []);

Custom Hooks

Create custom hooks for reusable logic:

// Good - Custom hook
export const useRepositories = (): UseRepositoriesResult => {
  const [repositories, setRepositories] = useState<GithubRepo[]>([]);
  const [loading, setLoading] = useState<boolean>(true);
  const [error, setError] = useState<Error | null>(null);

  const fetchData = async () => {
    try {
      setLoading(true);
      setError(null);
      const data = await fetchTopTypeScriptRepositories();
      setRepositories(data);
    } catch (err) {
      setError(err as Error);
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => {
    fetchData();
  }, []);

  return {
    repositories,
    loading,
    error,
    refetch: fetchData,
  };
};

// Usage
const {repositories, loading, error} = useRepositories();

Custom Hook Naming:

  • Always start with use prefix
  • Use camelCase: useRepositories, useAuth, useNavigation

Hook Dependencies

Always include all dependencies in dependency arrays:

// Good
useEffect(() => {
  fetchData(id);
}, [id]); // Include id in dependencies

// Bad - Missing dependencies
useEffect(() => {
  fetchData(id);
}, []); // Missing id dependency

// Good - Empty array for mount-only effects
useEffect(() => {
  // Initialize something once
}, []); // Intentionally empty

ESLint Rule: Enable react-hooks/exhaustive-deps to catch missing dependencies.

Error Handling

Try/Catch Patterns

Always handle errors in async operations:

// Good
const fetchData = async () => {
  try {
    setLoading(true);
    setError(null);
    const data = await fetchTopTypeScriptRepositories();
    setRepositories(data);
  } catch (err) {
    setError(err as Error);
    console.error('Error fetching repositories:', err);
  } finally {
    setLoading(false);
  }
};

// Bad - No error handling
const fetchData = async () => {
  const data = await fetchTopTypeScriptRepositories();
  setRepositories(data);
};

Error Boundaries

Use error boundaries for component-level error handling (when implemented):

// Error Boundary Component (class component required)
class ErrorBoundary extends React.Component<
  {children: React.ReactNode},
  {hasError: boolean}
> {
  constructor(props: {children: React.ReactNode}) {
    super(props);
    this.state = {hasError: false};
  }

  static getDerivedStateFromError() {
    return {hasError: true};
  }

  componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
    console.error('Error caught by boundary:', error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return <ErrorMessage message="Something went wrong" />;
    }
    return this.props.children;
  }
}

API Error Handling

Handle API errors at the service level:

// Good - Service level error handling
export const fetchTopTypeScriptRepositories = async (): Promise<GithubRepo[]> => {
  try {
    const response = await axios.get<SearchRepositoriesResponse>(
      `${API_BASE_URL}/search/repositories`,
      {params: {/* ... */}},
    );
    return response.data.items.map(mapRepoFromDTO);
  } catch (error) {
    console.error('Error fetching repositories:', error);
    throw error; // Re-throw for hook to handle
  }
};

// Good - Hook level error handling
export const useRepositories = (): UseRepositoriesResult => {
  const [error, setError] = useState<Error | null>(null);
  
  const fetchData = async () => {
    try {
      // ...
    } catch (err) {
      setError(err as Error);
    }
  };
  
  // ...
};

User-Facing Error Messages

Provide user-friendly error messages:

// Good
if (error) {
  return (
    <ErrorMessage
      message={error.message || 'Failed to load repositories. Please try again.'}
    />
  );
}

// Bad
if (error) {
  return <Text>{error.toString()}</Text>; // Technical error message
}

State Management

Local State vs Global State

Use Local State (useState) for:

  • Component-specific UI state
  • Form inputs
  • Toggle states
  • Temporary data
// Good - Local state
const [isExpanded, setIsExpanded] = useState<boolean>(false);
const [searchQuery, setSearchQuery] = useState<string>('');

Use Global State (Redux) for:

  • User authentication
  • App-wide settings
  • Shared data across multiple screens
  • API data that needs to be accessed from multiple components
  • Complex state that requires predictable updates

Context API Patterns

If using Context API (not currently implemented):

// Good - Context pattern
interface AppContextType {
  user: User | null;
  setUser: (user: User | null) => void;
}

const AppContext = createContext<AppContextType | undefined>(undefined);

export const useAppContext = () => {
  const context = useContext(AppContext);
  if (!context) {
    throw new Error('useAppContext must be used within AppProvider');
  }
  return context;
};

Redux State Management Patterns

The project uses Redux Toolkit for state management. Follow these patterns:

Store Structure

Organize Redux store by feature:

store/
├── index.ts                    # Store configuration
├── hooks.ts                    # Typed hooks
└── repositories/
    ├── repositories.slice.ts   # Slice with reducers
    ├── repositories.thunks.ts  # Async thunks
    └── repositories.types.ts   # Type definitions

Typed Hooks

Always use typed Redux hooks:

// Good - Typed hooks
import {useAppDispatch, useAppSelector} from '../store/hooks';

const dispatch = useAppDispatch();
const repositories = useAppSelector(state => state.repositories.repositories);

// Bad - Untyped hooks
import {useDispatch, useSelector} from 'react-redux';
const dispatch = useDispatch(); // No type safety

Redux Slices

Use Redux Toolkit slices for reducers:

// Good - Redux Toolkit slice
import {createSlice, PayloadAction} from '@reduxjs/toolkit';

const repositoriesSlice = createSlice({
  name: 'repositories',
  initialState,
  reducers: {
    setRepositories: (state, action: PayloadAction<GithubRepo[]>) => {
      state.repositories = action.payload;
    },
  },
});

Async Thunks

Use Redux Thunk for async operations:

// Good - Async thunk
import {createAsyncThunk} from '@reduxjs/toolkit';

export const fetchRepositories = createAsyncThunk(
  'repositories/fetchRepositories',
  async (_, {dispatch, rejectWithValue}) => {
    try {
      const data = await fetchTopTypeScriptRepositories();
      return data;
    } catch (error) {
      return rejectWithValue(error);
    }
  },
);

State Normalization

Keep state normalized and flat:

// Good - Normalized state
interface RepositoriesState {
  repositories: GithubRepo[];
  loading: boolean;
  error: Error | null;
}

// Avoid - Nested state
interface BadState {
  data: {
    repositories: {
      items: GithubRepo[];
    };
  };
}

Selectors

Use selectors for accessing state:

// Good - Selector in hook
const repositories = useAppSelector(state => state.repositories.repositories);
const loading = useAppSelector(state => state.repositories.loading);

// Good - Memoized selector (for complex calculations)
const selectTopRepositories = (state: RootState) =>
  state.repositories.repositories.slice(0, 5);

Dispatching Actions

Dispatch actions through thunks or direct actions:

// Good - Dispatch thunk
const dispatch = useAppDispatch();
useEffect(() => {
  dispatch(fetchRepositories());
}, [dispatch]);

// Good - Dispatch action
dispatch(setLoading(true));

Styling Conventions

Styled Components Usage

Always use styled-components for styling. Import from styled-components/native:

// Good - Using styled-components
import styled from 'styled-components/native';

const Container = styled.View`
  flex: 1;
  background-color: ${({theme}) => theme.colors.background};
  padding: ${({theme}) => theme.spacing.md}px;
`;

const Card = styled.View`
  background-color: ${({theme}) => theme.colors.surface};
  border-radius: ${({theme}) => theme.borderRadius.md}px;
  padding: ${({theme}) => theme.spacing.md}px;
`;

// Bad - Don't use StyleSheet
const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f5f5f5',
  },
});

Theme Integration

Always use theme values from the centralized theme:

// Good - Using theme
const Title = styled.Text`
  font-size: ${({theme}) => theme.typography.sizes.xl}px;
  font-weight: ${({theme}) => theme.typography.weights.bold};
  color: ${({theme}) => theme.colors.text.primary};
`;

// Bad - Hardcoded values
const Title = styled.Text`
  font-size: 20px;
  font-weight: bold;
  color: #1a1a1a;
`;

Component Styling Organization

Define styled components at the top of the file, before the main component:

// Good - Styled components defined first
import styled from 'styled-components/native';

const Container = styled.View`
  flex: 1;
  background-color: ${({theme}) => theme.colors.background};
`;

const Header = styled.View`
  background-color: ${({theme}) => theme.colors.primary};
  padding: ${({theme}) => theme.spacing.md}px;
`;

const HeaderTitle = styled.Text`
  font-size: ${({theme}) => theme.typography.sizes.xl}px;
  font-weight: ${({theme}) => theme.typography.weights.bold};
  color: ${({theme}) => theme.colors.text.inverse};
`;

// Main component
export const HomeView: React.FC<HomeViewProps> = ({repositories}) => {
  return (
    <Container>
      <Header>
        <HeaderTitle>Top TypeScript Repositories</HeaderTitle>
      </Header>
    </Container>
  );
};

Extending Styled Components

Extend existing styled components when creating variations:

// Good - Extending base component
const BaseCard = styled.View`
  background-color: ${({theme}) => theme.colors.surface};
  border-radius: ${({theme}) => theme.borderRadius.md}px;
  padding: ${({theme}) => theme.spacing.md}px;
`;

const ContainerCard = styled(BaseCard)`
  margin-horizontal: ${({theme}) => theme.spacing.md}px;
  margin-vertical: ${({theme}) => theme.spacing.sm}px;
`;

// Or extend from other styled components
const StyledCard = styled(Card)`
  margin-horizontal: ${({theme}) => theme.spacing.md}px;
`;

Conditional Styling

Use props for conditional styling:

// Good - Conditional styling with props
interface ButtonProps {
  variant?: 'primary' | 'secondary';
  disabled?: boolean;
}

const Button = styled.TouchableOpacity<ButtonProps>`
  padding: ${({theme}) => theme.spacing.md}px;
  background-color: ${({theme, variant}) =>
    variant === 'primary' ? theme.colors.primary : theme.colors.background};
  opacity: ${({disabled}) => (disabled ? 0.5 : 1)};
  border-radius: ${({theme}) => theme.borderRadius.md}px;
`;

Theme Provider Setup

Wrap the app with ThemeProvider in App.tsx:

// App.tsx
import {ThemeProvider} from 'styled-components/native';
import {theme} from './src/theme';

function App() {
  return (
    <ThemeProvider theme={theme}>
      {/* App content */}
    </ThemeProvider>
  );
}

Theme Structure

The theme is centralized in src/theme/:

// theme/theme.ts
export const theme = {
  colors: {
    primary: '#007AFF',
    background: '#f5f5f5',
    surface: '#ffffff',
    text: {
      primary: '#1a1a1a',
      secondary: '#666666',
      tertiary: '#888888',
      inverse: '#ffffff',
    },
    error: '#d32f2f',
  },
  spacing: {
    xs: 4,
    sm: 8,
    md: 16,
    lg: 24,
    xl: 32,
  },
  borderRadius: {
    sm: 8,
    md: 12,
    lg: 16,
  },
  typography: {
    sizes: {
      xs: 12,
      sm: 14,
      md: 16,
      lg: 18,
      xl: 20,
    },
    weights: {
      normal: '400',
      semibold: '600',
      bold: '700',
    },
  },
};

Responsive Design Patterns

Use theme spacing and relative units:

// Good - Using theme spacing
const Container = styled.View`
  flex: 1;
  padding: ${({theme}) => theme.spacing.md}px;
`;

const Row = styled.View`
  flex-direction: row;
  align-items: center;
  gap: ${({theme}) => theme.spacing.sm}px;
`;

const Card = styled.View`
  width: 100%;
  max-width: 600px;
  align-self: center;
`;

// Avoid - Fixed pixel values
const Container = styled.View`
  padding: 16px; // Bad - Use theme.spacing.md instead
`;

Style Props

Pass style props when needed for dynamic styling:

// Good - Accepting style prop for overrides
interface CardProps {
  children: React.ReactNode;
  style?: object;
}

const StyledCard = styled.View`
  background-color: ${({theme}) => theme.colors.surface};
  border-radius: ${({theme}) => theme.borderRadius.md}px;
`;

export const Card: React.FC<CardProps> = ({children, style}) => {
  return <StyledCard style={style}>{children}</StyledCard>;
};

Memory Management

useEffect Cleanup

Always clean up subscriptions, timers, and event listeners:

// Good - Cleanup subscriptions
useEffect(() => {
  const subscription = eventEmitter.subscribe(handleEvent);
  return () => subscription.unsubscribe();
}, []);

// Good - Cleanup timers
useEffect(() => {
  const timer = setInterval(() => {
    // Do something
  }, 1000);
  return () => clearInterval(timer);
}, []);

Avoiding Memory Leaks

Common Memory Leak Sources:

  1. Unclosed subscriptions:
// Bad
useEffect(() => {
  someObservable.subscribe(handleUpdate);
  // Missing cleanup
}, []);

// Good
useEffect(() => {
  const subscription = someObservable.subscribe(handleUpdate);
  return () => subscription.unsubscribe();
}, []);
  1. Uncancelled async operations:
// Bad
useEffect(() => {
  fetchData(); // May update state after unmount
}, []);

// Good
useEffect(() => {
  let cancelled = false;
  const fetchData = async () => {
    const data = await api.fetchData();
    if (!cancelled) {
      setData(data);
    }
  };
  fetchData();
  return () => {
    cancelled = true;
  };
}, []);
  1. Event listeners:
// Bad
useEffect(() => {
  window.addEventListener('resize', handleResize);
  // Missing cleanup
}, []);

// Good
useEffect(() => {
  const handleResize = () => {
    // Handle resize
  };
  window.addEventListener('resize', handleResize);
  return () => window.removeEventListener('resize', handleResize);
}, []);

Subscription Cleanup

Always return cleanup functions from useEffect:

// Good - Multiple cleanups
useEffect(() => {
  const subscription1 = source1.subscribe(handleUpdate1);
  const subscription2 = source2.subscribe(handleUpdate2);
  const timer = setInterval(handleTimer, 1000);
  
  return () => {
    subscription1.unsubscribe();
    subscription2.unsubscribe();
    clearInterval(timer);
  };
}, []);

Atomic Design Specific Rules

When to Create New Atoms

Create a new atom when:

  • You need a basic UI element that doesn't exist
  • The element will be reused in multiple places
  • It represents a single, indivisible UI component
  • It has no dependencies on other custom components

Examples:

  • Button, Input, Label, Icon, Card, Spacer

Don't create an atom for:

  • One-time use components (use inline JSX)
  • Complex components (use molecules/organisms)
  • Components that depend on other custom components

Molecule Composition Guidelines

Create molecules when:

  • You need to combine 2+ atoms into a functional unit
  • The combination serves a specific, reusable purpose
  • It doesn't require business logic or state management
  • It represents a common UI pattern

Examples:

  • RepositoryCard (Card + Text elements)
  • SearchBar (Input + Button + Icon)
  • FormField (Label + Input + ErrorMessage)

Guidelines:

  • Keep molecules simple and focused
  • Don't add business logic to molecules
  • Make molecules reusable across different contexts
  • Use props for customization

Organism Complexity Limits

Create organisms (containers) when:

  • You need to combine multiple molecules and atoms
  • The component requires business logic or state management
  • It represents a complete feature or screen
  • It's screen-specific and not meant for reuse

Guidelines:

  • Separate logic (screen.tsx) from presentation (view.tsx)
  • Keep screen files focused on state and side effects
  • Keep view files focused on rendering
  • Use custom hooks for data fetching
  • Don't put API calls directly in containers

Template vs Page Distinction

Templates (not currently used, but good to know):

  • Page-level layouts without real content
  • Define structure and placeholder content
  • Used for design and prototyping

Pages (our containers):

  • Specific instances with real content
  • Connected to data and state
  • What users actually see and interact with

In this project, we use containers directly as pages, skipping the template level for simplicity.

Composition Best Practices

  1. Atoms compose into Molecules:
// RepositoryCard (molecule) uses Card (atom)
export const RepositoryCard = ({repository}) => (
  <Card>
    <Text>{repository.name}</Text>
  </Card>
);
  1. Molecules compose into Organisms:
// HomeView (organism) uses RepositoryCard (molecule)
export const HomeView = ({repositories}) => (
  <FlatList
    data={repositories}
    renderItem={({item}) => <RepositoryCard repository={item} />}
  />
);
  1. Keep dependencies unidirectional:
  • Atoms don't depend on molecules or organisms
  • Molecules don't depend on organisms
  • Organisms can depend on molecules and atoms

File Organization Rules

  1. One component per file for atoms and molecules
  2. Multi-file structure for containers (screen, view, props, styles, types)
  3. Group related files in the same folder
  4. Use consistent naming across the project

Reusability Guidelines

  • Atoms: Highly reusable, no context-specific logic
  • Molecules: Reusable within similar contexts
  • Organisms: Screen-specific, not reusable

When in doubt about where a component belongs:

  1. Can it be used in multiple places? → Atom or Molecule
  2. Does it need business logic? → Organism (Container)
  3. Is it screen-specific? → Organism (Container)

Remember: These conventions are guidelines. Use your judgment, but maintain consistency. When introducing new patterns, update this document.