Hone logo
Hone
Problems

Implement a Custom useWatch Hook for React Form State

You're tasked with creating a custom React hook, useWatch, that allows you to subscribe to specific fields within a form's state. This hook is incredibly useful for triggering re-renders or performing side effects only when certain parts of your form data change, optimizing performance by avoiding unnecessary updates.

Problem Description

Your goal is to implement a useWatch hook that takes an array of field names (strings) and a form state object as input. The hook should return an object where keys are the watched field names and values are the current values of those fields from the form state. When any of the watched fields change in the form state, the useWatch hook should re-render the component that uses it, providing the updated values.

Key Requirements:

  • The hook should accept an array of strings, representing the names of the form fields to watch.
  • The hook should accept the current form state object.
  • The hook should return an object containing the values of the watched fields.
  • The hook must re-render the consuming component whenever any of the watched fields' values change.
  • If a watched field name does not exist in the form state, its value in the returned object should be undefined.
  • The hook should be efficient and avoid unnecessary re-renders when unrelated fields change.

Expected Behavior:

When a component uses useWatch(['fieldName1', 'fieldName2'], formState), it should receive an object like { fieldName1: value1, fieldName2: value2 }. If formState updates and value1 changes, the component should re-render with the new value1. If a field not present in formState is watched, its value in the returned object will be undefined.

Edge Cases to Consider:

  • What happens if the array of fields to watch is empty?
  • What happens if the formState object is initially empty or null/undefined?
  • How should the hook handle deeply nested fields if that were a requirement (though for this challenge, focus on top-level fields)?

Examples

Example 1:

// Assume this is your form state
const initialFormState = {
  firstName: 'John',
  lastName: 'Doe',
  age: 30,
};

// Component using useWatch
function MyForm(props: { formState: typeof initialFormState }) {
  const watchedFields = useWatch(['firstName', 'age'], props.formState);

  console.log('Watched Fields:', watchedFields);
  // Expected Output (initially): Watched Fields: { firstName: 'John', age: 30 }

  return (
    <div>
      {/* ... form elements ... */}
    </div>
  );
}

// Later, when form state updates:
const updatedFormState = {
  firstName: 'Jane',
  lastName: 'Doe',
  age: 31,
  email: 'jane@example.com'
};

// If MyForm re-renders with updatedFormState,
// the console.log would now show: Watched Fields: { firstName: 'Jane', age: 31 }

Explanation: The useWatch hook subscribes to firstName and age. Initially, it returns their values. When firstName and age change in the formState passed to MyForm, the hook detects this and causes MyForm to re-render with the updated watched fields.

Example 2:

// Assume this is your form state
const initialFormState = {
  username: 'coder123',
  password: 'securepassword',
};

// Component using useWatch
function AuthForm(props: { formState: typeof initialFormState }) {
  const watchedFields = useWatch(['username', 'nonExistentField'], props.formState);

  console.log('Watched Fields:', watchedFields);
  // Expected Output (initially): Watched Fields: { username: 'coder123', nonExistentField: undefined }

  return (
    <div>
      {/* ... form elements ... */}
    </div>
  );
}

// If formState updates to:
const updatedFormState = {
  username: 'coder123', // unchanged
  password: 'newpassword', // changed
  email: 'test@example.com'
};

// If AuthForm re-renders with updatedFormState,
// the console.log would still show: Watched Fields: { username: 'coder123', nonExistentField: undefined }
// because 'username' and 'nonExistentField' did not change, and 'password' is not watched.

Explanation: The hook watches username and nonExistentField. Since nonExistentField is not in the formState, its value is undefined. Even though password changes, it doesn't affect the output of useWatch because it's not being watched.

Constraints

  • The useWatch hook must be implemented using React's Hooks API (e.g., useState, useEffect, useRef).
  • The formState object can contain any valid JSON primitive types (string, number, boolean, null, undefined) or nested objects/arrays for its values. However, your implementation only needs to correctly track changes to top-level primitive values and their presence/absence.
  • Performance is important: avoid unnecessary re-renders. The component using useWatch should only re-render when the values of the watched fields change.
  • The hook should handle the case where the input fields array is empty gracefully (returning an empty object).
  • The hook should handle cases where formState is null or undefined without crashing (e.g., returning undefined for all watched fields).

Notes

  • Consider how to efficiently compare previous and current values of the watched fields to determine if a re-render is necessary.
  • You'll likely need to store the previous values of the watched fields to perform these comparisons. useRef is a good candidate for this.
  • Think about how to correctly trigger a re-render in React.
  • This is a simplified version of a useWatch hook often found in form management libraries. Focus on the core functionality of watching specific fields and re-rendering on change.
Loading editor...
typescript