Frontend Common Utilities

Common utilities are reusable functions and values that encapsulate frequently used application logic. They promote code reuse, maintainability, and consistency across the application.

Locating Existing Utilities

Before creating a new utility, check if similar functionality already exists in the codebase. Think about other functionalities that might solve the same logic and investigate how they're implemented.

Example: When implementing scaling for new Plant Markers, check if existing Plant Labels already have scaling logic.

Search Strategy:

  • Look in features/*/utils and src/utils directories
  • Search for relevant keywords using file search or regex search
  • Examine similar components for comparable logic

When to Create Common Utilities

Create common utilities when application logic is duplicated or likely to become duplicated. Extract duplicated logic into functions, focusing on core logic that can be abstracted from component context.

Key Indicators:

  • Same logic appears in multiple places
  • New features will require similar functionality
  • Logic is non-trivial or is making a design decision

How to Create Common Utilities

Location: Determine the appropriate file by asking:

  • Is my utility scoped to a feature? Yes → features/my_feature/utils, No → src/utils
  • What object is it related to? (Date, Layer, Planting, etc.)
  • Is there already an object-utils.ts file for it?

Interface:

Common utility functions should have clean, understandable interfaces.

  • Name: Function should be named descriptively. The name should describe what it does, rather than where or how it is used. Example: capitalizeFirstLetter and not prepareCommonPlantNameForDisplay.

  • Arguments: There should be no more than three arguments. When 4+ arguments are needed, group them into object arguments. Arguments should also be grouped into an object if there are 3 or less arguments but some of them have exactly the same type (e.g., boolean). An exception to this is equality function (e.g., arePlantsEqual).

  • Return value: Functions should return a single, clearly typed value. Avoid returning loosely typed values such as any, unknown. If a function can fail or produce no result, prefer returning null or undefined over throwing an error, and document which case returns null or undefined.

Implementation:

Common utility functions should have readable, straightforward implementation.

  • Use meaningful names for variables
  • Keep implementation simple and understandable
  • Write comments to explain non-obvious WHYs
  • Make functions pure when possible (no side effects)
  • Use TypeScript for type safety

Documentation:

Each common utility hook or function should be documented with JSDoc comments explaining:

  • What the utility does and what is the expected result
  • The rationale behind the expected result (if applicable)
  • When the utility should be used (if not obvious from its name)

This reinforces utility stability by making it easier to understand whether behavioral changes are accidental (comment unchanged) or intentional (comment updated).

Testing: Add unit tests for non-trivial utilities. Place tests in the same directory structure (e.g., date-utils.test.ts alongside date-utils.ts).

Example: String Utilities

The string-utils.ts file demonstrates good utility function practices:

Location: Placed in src/utils/ since it provides general string manipulation functions used across features.

Meaningful Names:

  • capitalizeFirstLetter() - Clear name indicating exactly what the function does
  • capitalizeText() - Descriptive name for title case formatting

Documentation: Each function includes JSDoc comments explaining the purpose and behavior.

Pure Functions: Both functions are side-effect free and return predictable outputs for given inputs.

Type Safety: Uses TypeScript with explicit parameter and return types.

Testing: Includes comprehensive tests in string-utils.test.tsx covering normal cases and edge cases (undefined, empty arrays).