React Custom Hook: useNumber
Build a custom React hook, useNumber, that manages and manipulates numerical state. This hook should provide a convenient way to handle number values, including incrementing, decrementing, and resetting them, along with the current value.
Problem Description
Your task is to implement a custom React hook named useNumber in TypeScript. This hook should encapsulate the logic for managing a numerical state within a React component. It should provide:
- Current Value: The current numerical value being managed.
- Increment Function: A function to increase the current value by a specified amount (defaulting to 1).
- Decrement Function: A function to decrease the current value by a specified amount (defaulting to 1).
- Reset Function: A function to reset the current value back to its initial value.
This hook will be useful for scenarios where you need to manage counters, quantities, scores, or any other numeric input that requires interactive manipulation.
Key Requirements:
- The hook should accept an
initialValueas an argument, which will be the starting number. - The hook should return an object containing:
value: The current number.increment: A function that takes an optionalamount(number) and increasesvalueby that amount. Defaults to incrementing by 1.decrement: A function that takes an optionalamount(number) and decreasesvalueby that amount. Defaults to decrementing by 1.reset: A function that resetsvalueto the originalinitialValue.
- The hook must be written in TypeScript and strongly typed.
Examples
Example 1: Basic Counter
import { renderHook, act } from '@testing-library/react';
import { useNumber } from './useNumber'; // Assuming you save the hook in this file
function CounterComponent() {
const { value, increment, decrement, reset } = useNumber(0);
return (
<div>
<p>Count: {value}</p>
<button onClick={() => increment()}>Increment</button>
<button onClick={() => decrement()}>Decrement</button>
<button onClick={() => reset()}>Reset</button>
</div>
);
}
// In your test file:
describe('useNumber', () => {
it('should manage a basic counter', () => {
const { result } = renderHook(() => useNumber(0));
expect(result.current.value).toBe(0);
act(() => {
result.current.increment();
});
expect(result.current.value).toBe(1);
act(() => {
result.current.increment(5);
});
expect(result.current.value).toBe(6);
act(() => {
result.current.decrement();
});
expect(result.current.value).toBe(5);
act(() => {
result.current.decrement(2);
});
expect(result.current.value).toBe(3);
act(() => {
result.current.reset();
});
expect(result.current.value).toBe(0);
});
});
Explanation:
The useNumber hook is initialized with 0. Incrementing by default increases it by 1. Incrementing with a specific amount adds that amount. Decrementing works similarly. Resetting returns it to the initial 0.
Example 2: Starting with a different number
// In your test file:
describe('useNumber', () => {
it('should initialize with a different number', () => {
const { result } = renderHook(() => useNumber(10));
expect(result.current.value).toBe(10);
act(() => {
result.current.increment();
});
expect(result.current.value).toBe(11);
act(() => {
result.current.reset();
});
expect(result.current.value).toBe(10);
});
});
Explanation:
The hook is initialized with 10. Incrementing increases it, and resetting brings it back to 10.
Example 3: Using larger increments/decrements
// In your test file:
describe('useNumber', () => {
it('should handle larger increments and decrements', () => {
const { result } = renderHook(() => useNumber(100));
expect(result.current.value).toBe(100);
act(() => {
result.current.decrement(50);
});
expect(result.current.value).toBe(50);
act(() => {
result.current.increment(75);
});
expect(result.current.value).toBe(125);
act(() => {
result.current.reset();
});
expect(result.current.value).toBe(100);
});
});
Explanation: Demonstrates using specific, larger amounts for incrementing and decrementing, showing the flexibility of the hook.
Constraints
- The
initialValueand anyamountpassed toincrementordecrementmust be numbers. - The hook should be implemented using React's
useStatehook internally. - The functions returned by the hook (
increment,decrement,reset) should be stable (i.e., not change on every render) if possible, to avoid unnecessary re-renders in consuming components.
Notes
- Consider how to handle the
amountparameter forincrementanddecrement. What should happen if it's not provided? - Ensure your TypeScript types are accurate and expressive.
- Think about the stability of the returned functions. Using
useCallbackmight be beneficial.