Hone logo
Hone
Problems

Implement a Custom useFormState Hook in React

This challenge asks you to build a custom React hook that mimics the functionality of a useFormState hook. This type of hook is invaluable for managing form states, handling submissions, and providing feedback to the user, especially in conjunction with server actions or similar asynchronous operations.

Problem Description

Your task is to implement a custom hook called useFormState in TypeScript. This hook should manage the state of a form, track its submission status, and capture any errors or data returned from a submission function.

Key Requirements:

  1. State Management: The hook should accept an initial form state and a submission function.
  2. Submission Function: The submission function will be an asynchronous function (or a function that returns a Promise) that takes the current form state as an argument and returns a result (which could be data or an error).
  3. State Updates: The hook should expose a way to dispatch an action that triggers the submission function. This dispatch function should update the form state with the result of the submission.
  4. Return Value: The hook should return an object containing:
    • formState: The current state of the form.
    • isSubmitting: A boolean indicating whether the submission is currently in progress.
    • error: Any error caught during the submission process.
    • data: Any successful data returned by the submission function.
    • dispatch: A function to trigger the form submission.

Expected Behavior:

  • When dispatch is called, isSubmitting should become true.
  • The provided submission function should be invoked with the current formState.
  • Upon successful completion of the submission function, isSubmitting should become false, and data should be populated with the returned value.
  • If the submission function throws an error, isSubmitting should become false, and error should be populated with the caught error.
  • The formState should be updated to reflect the result of the submission (either data or error).

Edge Cases:

  • Handling multiple dispatches while a submission is already in progress (the hook should ideally queue or ignore subsequent dispatches until the current one finishes, or behave predictably).
  • Ensuring proper cleanup of any ongoing asynchronous operations if the component unmounts before the submission completes (though for this challenge, we can focus on the core state management).

Examples

Let's assume we have a simple form with a single text input.

Example 1: Successful Submission

// Mock submission function
const mockSubmitFn = async (state: { message: string }) => {
  await new Promise(resolve => setTimeout(resolve, 100)); // Simulate network delay
  if (state.message.toLowerCase().includes("success")) {
    return { success: true, processedMessage: state.message.toUpperCase() };
  }
  throw new Error("This is not a success message.");
};

// Inside a React component
const initialFormState = { message: "" };
const { formState, isSubmitting, error, data, dispatch } = useFormState(mockSubmitFn, initialFormState);

// Initial render:
// formState = { message: "" }
// isSubmitting = false
// error = undefined
// data = undefined

// User types "Hello, this is a success message." into an input and the input's onChange updates formState.
// User clicks a submit button, which calls dispatch({ message: "Hello, this is a success message." })

// During submission:
// isSubmitting = true

// After submission:
// isSubmitting = false
// error = undefined
// data = { success: true, processedMessage: "HELLO, THIS IS A SUCCESS MESSAGE." }
// formState = { message: "Hello, this is a success message.", data: { success: true, processedMessage: "HELLO, THIS IS A SUCCESS MESSAGE." } } (assuming formState is updated with data)

Example 2: Submission with Error

// Mock submission function (same as above)
const mockSubmitFn = async (state: { message: string }) => {
  await new Promise(resolve => setTimeout(resolve, 100)); // Simulate network delay
  if (state.message.toLowerCase().includes("success")) {
    return { success: true, processedMessage: state.message.toUpperCase() };
  }
  throw new Error("This is not a success message.");
};

// Inside a React component
const initialFormState = { message: "" };
const { formState, isSubmitting, error, data, dispatch } = useFormState(mockSubmitFn, initialFormState);

// User types "This will fail." into an input and the input's onChange updates formState.
// User clicks a submit button, which calls dispatch({ message: "This will fail." })

// During submission:
// isSubmitting = true

// After submission:
// isSubmitting = false
// error = Error: This is not a success message.
// data = undefined
// formState = { message: "This will fail.", error: Error: This is not a success message. } (assuming formState is updated with error)

Example 3: Multiple Dispatches (Conceptual)

If dispatch is called twice rapidly while a submission is in progress, the behavior depends on the implementation. A robust implementation might only process the latest dispatch after the current one completes, or it might ignore subsequent dispatches. For this challenge, a simple sequential processing where only the last dispatched value is considered once the previous submission finishes is acceptable.

Constraints

  • The useFormState hook must be implemented in TypeScript.
  • The submission function passed to the hook is expected to be an async function or a function returning a Promise.
  • The hook should not introduce significant performance overhead beyond what is typical for React hooks.
  • The formState should be a deep copy or managed immutably to prevent unexpected side effects.

Notes

  • Consider how you will handle the update to the formState itself. Should it simply be the initial state, or should it also incorporate the data or error returned from the submission? A common pattern is to merge the returned data or error into the formState.
  • Think about how to manage the isSubmitting flag to prevent race conditions or multiple submissions.
  • The type for FormState should be generic, allowing it to hold any shape of form data.
  • The return type of the submission function should also be considered for generic typing.
Loading editor...
typescript