Hone logo
Hone
Problems

Typed State Management with Discriminant Unions

This challenge focuses on implementing a robust and type-safe state management system in TypeScript. You'll leverage discriminant unions to accurately represent different states of an application or component, ensuring that operations are only performed when the state is appropriate. This pattern is crucial for building predictable and maintainable applications, especially in complex UIs or data-driven workflows.

Problem Description

You are tasked with creating a type-safe way to manage the state of a user profile feature. The profile can be in one of several distinct states: Loading, Loaded (with user data), or Error (with an error message).

Your goal is to:

  1. Define the states: Create TypeScript types that accurately represent these three states using a discriminant union. Each state should have a unique type property to act as the discriminant.
  2. Implement a state handler function: Create a function that accepts the current state and an action (which might be to fetch data, update data, or handle an error). This function should return the new state.
  3. Enforce type safety: Ensure that the handler function can only perform valid operations based on the current state. For example, you should not be able to access user data if the state is Loading or Error.
  4. Handle transitions: The handler function should correctly transition between states based on the input action.

Key Requirements:

  • Discriminant Union: Use a union type with a common type property to represent the different states.
  • State Transitions: The handler function should process actions and return a new state based on the current state and the action.
  • Type Safety: The TypeScript compiler should prevent invalid access to state properties.

Expected Behavior:

  • When the state is Loading, only a "data loaded" or "error occurred" action should be processed.
  • When the state is Loaded, actions like "update user" or "clear profile" might be valid.
  • When the state is Error, actions to retry fetching or clear the error should be valid.

Edge Cases to Consider:

  • What happens if an action is dispatched that is not applicable to the current state (e.g., trying to update user data when the state is Error)? The handler should ideally return the current state or a sensible default.

Examples

Example 1: Initial Loading State

type UserProfileState =
  | { type: 'Loading' }
  | { type: 'Loaded'; user: { id: string; name: string } }
  | { type: 'Error'; message: string };

// Initial state
const initialState: UserProfileState = { type: 'Loading' };

// Action: Data successfully loaded
const loadSuccessAction = { type: 'LOAD_SUCCESS', user: { id: '123', name: 'Alice' } };

// Handler function (implementation details omitted for now)
// function handleProfileState(currentState: UserProfileState, action: any): UserProfileState { ... }

// Expected outcome if handleProfileState is called with initialState and loadSuccessAction
// Output: { type: 'Loaded', user: { id: '123', name: 'Alice' } }

Example 2: Loaded State and Error Transition

// Assuming the current state is Loaded from Example 1
const currentState: UserProfileState = { type: 'Loaded', user: { id: '123', name: 'Alice' } };

// Action: An error occurred during a subsequent operation
const loadErrorAction = { type: 'LOAD_ERROR', message: 'Network connection failed' };

// Expected outcome if handleProfileState is called with currentState and loadErrorAction
// Output: { type: 'Error', message: 'Network connection failed' }

Example 3: Error State and Retry Action

// Assuming the current state is Error
const currentState: UserProfileState = { type: 'Error', message: 'Network connection failed' };

// Action: Retry fetching data
const retryAction = { type: 'RETRY_LOAD' };

// Expected outcome if handleProfileState is called with currentState and retryAction
// Output: { type: 'Loading' }

Constraints

  • The solution must be written entirely in TypeScript.
  • The state types must utilize a discriminant union pattern.
  • The handler function should have a clear signature for handling state transitions.
  • The solution should be well-commented, explaining the purpose of each type and function.
  • No external libraries are allowed for state management.

Notes

Consider how you will define the actions that trigger state changes. You might need to define separate action types as well, or infer them within the handler. Think about how the switch statement or similar control flow structures in TypeScript can leverage the discriminant property (type) to narrow down the type of the currentState within the handler function. This is where the power of TypeScript's type inference will shine.

Loading editor...
typescript