Code Organization
Core Principles
1. Separation of Actions from Calculations
- 
Calculations (Pure Functions) - Return the same output for the same input
- Have no side effects
- Can be safely unit tested
- Examples: data transformations, validations, computations
 
- 
Actions (Functions with Side Effects) - Interact with the outside world
- May have different results for the same input
- Need to be carefully managed
- Examples: API calls, database operations, state updates
 
2. First-Class Functions
- Actions and calculations are defined as separate functions that can be passed around
- Functions are composed together in hooks for maximum reusability
- Example:
// Calculation (Pure Function)
 const filterActiveUsers = (users) => users.filter(user => user.isActive);
 // Action
 const useUserManagement = () => {
 const [users, setUsers] = useState([]);
 
 // Composing functions
 const getActiveUsers = () => filterActiveUsers(users);
 
 return { users, getActiveUsers };
 };
3. Pure Functions
- All calculations must be pure functions
- Same input always produces same output
- No side effects or external state dependencies
- Example:
// Pure Function
 const calculateTotal = (items) => {
 return items.reduce((sum, item) => sum + item.price, 0);
 };
 // Impure Function (depends on external state)
 const calculateTotalWithTax = (items) => {
 return items.reduce((sum, item) => sum + item.price, 0) * globalTaxRate;
 };
4. Immutability
- Never modify state directly
- Create new state objects using spread operator
- Use array methods that return new arrays (map, filter, reduce)
- Example:
// Immutable update
 const addItem = (items, newItem) => [...items, newItem];
 // Mutable update
 const addItem = (items, newItem) => {
 items.push(newItem); // Mutates original array
 return items;
 };
5. Unidirectional Data Flow
- Clear flow: State → Calculations → UI
- State changes only through defined actions
- Example:
const useTaskManager = () => {
 // State
 const [tasks, setTasks] = useState([]);
 
 // Calculations
 const incompleteTasks = () => tasks.filter(task => !task.completed);
 const completedTasks = () => tasks.filter(task => task.completed);
 
 // Actions
 const addTask = (task) => setTasks([...tasks, task]);
 const toggleTask = (taskId) => {
 setTasks(tasks.map(task =>
 task.id === taskId
 ? { ...task, completed: !task.completed }
 : task
 ));
 };
 
 return {
 // Expose calculated values
 incomplete: incompleteTasks(),
 completed: completedTasks(),
 // Expose actions
 addTask,
 toggleTask
 };
 };
6. Abstraction
- Complex logic hidden behind hooks
- Components only depend on the hook's interface
- Example:
// Complex logic hidden in hook
 const useDataProcessor = () => {
 // ... complex data processing logic ...
 return {
 processedData,
 isLoading,
 error,
 process
 };
 };
 // Simple component interface
 const DataView = () => {
 const { processedData, isLoading, error, process } = useDataProcessor();
 
 if (isLoading) return <Loading />;
 if (error) return <Error message={error} />;
 
 return <DisplayData data={processedData} onProcess={process} />;
 };
React Hooks Organization
We follow a layered approach to organize our React hooks:
1. Business Logic Hooks (Custom Hooks)
- Separate business logic from UI components
- Handle complex state management
- Manage API calls and data transformations
- Example:
const useUserData = (userId: string) => {
 // Business logic for fetching and managing user data
 const [userData, setUserData] = useState(null);
 
 // Separate calculations
 const transformUserData = (rawData) => {
 // Pure function to transform data
 return /* transformed data */;
 };
 
 // Actions are clearly identified
 const fetchUser = async () => {
 const data = await api.getUser(userId);
 setUserData(transformUserData(data));
 };
 
 return { userData, fetchUser };
 };
2. UI Component Hooks
- Focus on presentation logic
- Handle UI state and interactions
- Example:
const UserProfile = ({ userId }) => {
 // Business logic is separated into custom hook
 const { userData, fetchUser } = useUserData(userId);
 
 // UI-specific state
 const [isEditing, setIsEditing] = useState(false);
 
 return (/* UI JSX */);
 };
Testing Strategy
Testing Calculations
- Write comprehensive unit tests
- Test all edge cases
- No need for mocking
describe('filterActiveUsers', () => {
  it('should return only active users', () => {
    const users = [
      { id: 1, isActive: true },
      { id: 2, isActive: false }
    ];
    expect(filterActiveUsers(users)).toEqual([{ id: 1, isActive: true }]);
  });
});
Testing Actions
- Use dependency injection for testability
- Mock external dependencies
- Test side effects
describe('useTaskManager', () => {
  it('should properly manage task state', () => {
    const { result } = renderHook(() => useTaskManager());
    
    act(() => {
      result.current.addTask({ id: 1, text: 'Test', completed: false });
    });
    
    expect(result.current.incomplete).toHaveLength(1);
    expect(result.current.completed).toHaveLength(0);
  });
});
Benefits
- Improved testability through pure functions
- Clear separation of concerns
- Predictable state management
- Highly reusable code
- Self-documenting architecture
- Reduced bugs through immutability