Hone logo
Hone
Problems

Implementing the useImperativeHandle Hook in React

The useImperativeHandle hook allows you to customize the instance value that is exposed to parent components when using refs. This is particularly useful when you want to provide a specific API to a parent component, rather than exposing the entire component instance. This challenge will guide you through creating a custom hook that leverages useImperativeHandle to expose a controlled API.

Problem Description

Your task is to create a custom hook called useControlledComponent that wraps a component and exposes a specific set of functions to its parent component via a ref. The hook should accept a component as an argument and return an object containing:

  1. A ref object that can be attached to the wrapped component.
  2. A function triggerAction that, when called, will invoke a predefined action within the wrapped component.

The wrapped component should internally manage a state variable called count. The triggerAction function, when called, should increment the count state by 1. The parent component should be able to access the current value of count through the ref.

Key Requirements:

  • The useControlledComponent hook must correctly implement useImperativeHandle.
  • The triggerAction function must increment the count state within the wrapped component.
  • The parent component must be able to access the count state through the ref.
  • The exposed API should only include the triggerAction function and the count property.

Expected Behavior:

When the parent component calls triggerAction through the ref, the count state within the wrapped component should increment, and the parent component should be able to observe this change.

Edge Cases to Consider:

  • What happens if the component passed to useControlledComponent doesn't have a count state? (Assume it does for this challenge, but consider it for future improvements).
  • How does useImperativeHandle handle updates to the exposed functions?

Examples

Example 1:

Input:
Wrapped Component:
function MyComponent() {
  const [count, setCount] = React.useState(0);

  return <div>Count: {count}</div>;
}

useControlledComponent(MyComponent)

Parent Component:
import React, { useRef } from 'react';
import useControlledComponent from './useControlledComponent';
import MyComponent from './MyComponent';

function ParentComponent() {
  const componentRef = useRef<any>(null);
  const controlledComponent = useControlledComponent(MyComponent);

  React.useEffect(() => {
    controlledComponent.ref.current.triggerAction();
  }, []);

  return (
    <div>
      <MyComponent ref={controlledComponent.ref} />
      <p>Count from parent: {controlledComponent.ref.current.count}</p>
    </div>
  );
}

Output: The Count: 1 is displayed in the MyComponent. The Count from parent: 1 is displayed in the ParentComponent.

Explanation: The triggerAction function increments the count state in MyComponent. The parent component accesses the updated count value through the ref.

Example 2:

Input:
Wrapped Component:
function MyComponent() {
  const [count, setCount] = React.useState(0);

  return <div>Count: {count}</div>;
}

useControlledComponent(MyComponent)

Parent Component:
import React, { useRef } from 'react';
import useControlledComponent from './useControlledComponent';
import MyComponent from './MyComponent';

function ParentComponent() {
  const componentRef = useRef<any>(null);
  const controlledComponent = useControlledComponent(MyComponent);

  React.useEffect(() => {
    controlledComponent.ref.current.triggerAction();
    controlledComponent.ref.current.triggerAction();
  }, []);

  return (
    <div>
      <MyComponent ref={controlledComponent.ref} />
      <p>Count from parent: {controlledComponent.ref.current.count}</p>
    </div>
  );
}

Output: The Count: 2 is displayed in the MyComponent. The Count from parent: 2 is displayed in the ParentComponent.

Explanation: Two calls to triggerAction increment the count state to 2.

Constraints

  • The solution must be written in TypeScript.
  • The useControlledComponent hook must correctly utilize useImperativeHandle.
  • The exposed API must only include the triggerAction function and the count property.
  • The wrapped component must manage its own state.
  • The solution should be relatively concise and readable.

Notes

  • Remember that useImperativeHandle allows you to customize the instance value exposed to parent components through refs.
  • Consider how useRef and useImperativeHandle work together to achieve the desired behavior.
  • Think about the lifecycle of the component and how the exposed API should be updated.
  • The triggerAction function is a simplified example; in a real-world scenario, it could perform more complex operations.
  • Focus on correctly exposing the triggerAction function and the count state through the ref.
Loading editor...
typescript