Crafting a Reusable useController Hook in React
This challenge focuses on building a custom React hook, useController, that manages state and provides a standardized interface for controlling components. This hook will encapsulate common logic for managing a controlled component, such as a text input or a select dropdown, making your code cleaner and more reusable. It's a valuable exercise in understanding React hooks and state management.
Problem Description
You are tasked with creating a useController hook in TypeScript that simplifies the management of controlled components in React. The hook should accept an initial value and a onChange callback function as arguments. It should internally manage the state of the controlled component and provide a value and onChange prop that can be passed to the component. The onChange callback provided to the hook should be invoked whenever the component's value changes.
Key Requirements:
- State Management: The hook must internally manage the state of the controlled component using
useState. - Value Propagation: The hook must expose the current value of the controlled component via the
valueproperty. - Change Handling: The hook must expose an
onChangefunction that, when called, updates the internal state and invokes the providedonChangecallback. - TypeScript Safety: The hook must be written in TypeScript and provide type safety for the initial value, the value, and the
onChangecallback. - Return Values: The hook should return an object containing the
valueand theonChangefunction.
Expected Behavior:
When the useController hook is called, it should initialize the state with the provided initial value. The value property should reflect the current state. Calling the onChange function should update the state with the new value and execute the provided onChange callback.
Edge Cases to Consider:
- Initial Value Type: The hook should handle different data types for the initial value (string, number, boolean, etc.).
onChangeCallback: TheonChangecallback should be invoked with the new value.- No Initial Value: Consider what should happen if no initial value is provided (default to an empty string or null, for example).
Examples
Example 1:
Input: useController<string>("initial value")
Output: { value: "initial value", onChange: (newValue: string) => void }
Explanation: The hook initializes the state with "initial value" and returns an object with the current value and an onChange function.
Example 2:
Input: useController<number>(0, (newValue) => console.log("New number:", newValue))
Output: { value: 0, onChange: (newValue: number) => void }
Explanation: The hook initializes the state with 0 and returns an object with the current value and an onChange function that logs the new value to the console.
Example 3: (Edge Case - No Initial Value)
Input: useController<string>()
Output: { value: "", onChange: (newValue: string) => void }
Explanation: The hook initializes the state with an empty string and returns an object with the current value and an onChange function.
Constraints
- The hook must be written in TypeScript.
- The
onChangecallback provided to the hook must be a function that accepts a single argument (the new value) and returnsvoid. - The hook should be reusable with different data types for the controlled component's value.
- The hook should not introduce any unnecessary dependencies.
Notes
- Think about how to make the hook generic to handle different data types.
- Consider using the
useStatehook to manage the component's state. - The
onChangecallback provided to the hook is intended to be used for side effects or further processing of the new value. It's not responsible for updating the state itself. - Focus on creating a clean, well-documented, and reusable hook. Error handling is not required for this challenge.