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:
- A
refobject that can be attached to the wrapped component. - A function
triggerActionthat, 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
useControlledComponenthook must correctly implementuseImperativeHandle. - The
triggerActionfunction must increment thecountstate within the wrapped component. - The parent component must be able to access the
countstate through theref. - The exposed API should only include the
triggerActionfunction and thecountproperty.
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
useControlledComponentdoesn't have acountstate? (Assume it does for this challenge, but consider it for future improvements). - How does
useImperativeHandlehandle 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
useControlledComponenthook must correctly utilizeuseImperativeHandle. - The exposed API must only include the
triggerActionfunction and thecountproperty. - The wrapped component must manage its own state.
- The solution should be relatively concise and readable.
Notes
- Remember that
useImperativeHandleallows you to customize the instance value exposed to parent components throughrefs. - Consider how
useRefanduseImperativeHandlework together to achieve the desired behavior. - Think about the lifecycle of the component and how the exposed API should be updated.
- The
triggerActionfunction is a simplified example; in a real-world scenario, it could perform more complex operations. - Focus on correctly exposing the
triggerActionfunction and thecountstate through theref.