use-good-hooks
v1.0.42
Published
   => {
const [searchTerm, setSearchTerm] = useState('');
const debouncedSearchTerm = useDebounce(searchTerm, 500); // 500ms delay
// API call will only happen 500ms after the user stops typing
useEffect(() => {
if (debouncedSearchTerm) {
searchAPI(debouncedSearchTerm);
}
}, [debouncedSearchTerm]);
return (
<input
type="text"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="Search..."
/>
);
};Parameters
value: The value to debouncedelay: (Optional) The delay in milliseconds (default: 300ms)
Returns
- The debounced value
useDebounceFn
Creates a debounced version of a function. This hook ensures that a function is only executed after a specified period of inactivity, preventing it from being called too frequently. It's ideal for handling events like button clicks or API triggers that should not fire on every user action.
import useDebounceFn from 'use-good-hooks/use-debounce-fn';
const SaveButton = () => {
const [status, setStatus] = useState('Idle');
const debouncedSave = useDebounceFn(() => {
setStatus('Saving...');
// Simulate API call
setTimeout(() => setStatus('Saved!'), 1000);
}, 1000); // 1000ms delay
const handleClick = () => {
setStatus('Waiting...');
debouncedSave();
};
return (
<div>
<button onClick={handleClick}>Save Changes</button>
<p>Status: {status}</p>
</div>
);
};Parameters
fn: The function to debouncedelay: (Optional) The delay in milliseconds (default: 300ms)
Returns
- The debounced function.
useThrottle
Limits the rate at which a value can update. Useful for scroll events, window resizing, and other high-frequency events.
import useThrottle from 'use-good-hooks/use-throttle';
const ScrollTracker = () => {
const [scrollY, setScrollY] = useState(0);
const throttledScrollY = useThrottle(scrollY, 200); // 200ms throttle
useEffect(() => {
const handleScroll = () => {
setScrollY(window.scrollY);
};
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
return <div>Throttled scroll position: {throttledScrollY}px</div>;
};Parameters
value: The value to throttledelay: (Optional) The throttle interval in milliseconds (default: 300ms)
Returns
- The throttled value
useThrottleFn
Creates a throttled version of a function, limiting its execution to at most once per specified interval. It is useful for performance-critical scenarios like handling mouse movements, scrolling, or window resizing events without overwhelming the browser.
import useThrottleFn from 'use-good-hooks/use-throttle-fn';
const MouseTracker = () => {
const [position, setPosition] = useState({ x: 0, y: 0 });
const throttledMouseMove = useThrottleFn((event) => {
setPosition({ x: event.clientX, y: event.clientY });
}, 300); // Update at most every 300ms
useEffect(() => {
window.addEventListener('mousemove', throttledMouseMove);
return () => window.removeEventListener('mousemove', throttledMouseMove);
}, [throttledMouseMove]);
return (
<div>
Throttled mouse position: X: {position.x}, Y: {position.y}
</div>
);
};Parameters
fn: The function to throttledelay: (Optional) The throttle interval in milliseconds (default: 300ms)
Returns
- The throttled function.
usePrev
Captures the previous value of a state or prop. Useful for comparing changes between renders.
import usePrev from 'use-good-hooks/use-prev';
const Counter = ({ count }) => {
const prevCount = usePrev(count);
return (
<div>
<p>Current count: {count}</p>
<p>Previous count: {prevCount ?? 'None'}</p>
<p>Direction: {count > prevCount ? 'Increasing' : count < prevCount ? 'Decreasing' : 'No change'}</p>
</div>
);
};Parameters
value: The value to track
Returns
- The previous value (undefined on first render)
useHistoryState
Tracks the history of a state value, providing undo and redo capabilities. This is perfect for building editors, forms, or any UI where users might want to reverse their actions.
import useHistoryState from 'use-good-hooks/use-history-state';
const TextEditor = () => {
const [state, actions] = useHistoryState('', {
maxCapacity: 10,
debounceMs: 0 // Disable debouncing for immediate updates
});
return (
<div>
<textarea
value={state.present}
onChange={(e) => actions.set(e.target.value)}
rows={4}
cols={50}
/>
<div>
<button onClick={actions.undo} disabled={!state.canUndo}>
Undo
</button>
<button onClick={actions.redo} disabled={!state.canRedo}>
Redo
</button>
<button onClick={actions.initial}>Initial State</button>
</div>
<p>Past states: {state.past.length}</p>
<p>Future states: {state.future.length}</p>
</div>
);
};Parameters
initialState: The initial state valueoptions: (Optional) Configuration options:debounceMs: Time in milliseconds to debounce the state changes (default: 250)debounceSettings: Debounce settings object from LodashmaxCapacity: Maximum number of history entries to keep (default: 10)onChange: Function to call when the state changes, receives{ action, state }paused: Boolean indicating if the history is paused (default: false)
Returns
Returns a tuple [historyState, historyActions]:
historyState (Object): - canRedo: Boolean indicating if redo is possible - canUndo: Boolean indicating if undo is possible - future: Array of future states (for redo) - past: Array of past states (for undo) - paused: Boolean indicating if the history is paused - present: The current state value
historyActions (Object): - initial: Function to reset to initial state - pause: Function to pause history tracking (updates won't be recorded) - redo: Function to move to the next state (redo) - replace: Function to replace the state without adding to history - resume: Function to resume history tracking - set: Function to update the state and record history (debounced by default) - setDirect: Function to update the state immediately, bypassing debounce - undo: Function to move to the previous state (undo)
Example with onChange callback
const Editor = () => {
const [state, actions] = useHistoryState('', {
onChange: ({ action, state }) => {
console.log(`Action: ${action}, State: ${state}`);
// Action can be: 'SET', 'UNDO', 'REDO', 'INITIAL', 'REPLACE'
}
});
return (
<textarea
value={state.present}
onChange={(e) => actions.set(e.target.value)}
/>
);
};Example with pause/resume
const Form = () => {
const [state, actions] = useHistoryState({ name: '', email: '' });
const handleBulkUpdate = () => {
actions.pause(); // Pause history tracking
actions.set({ name: 'John', email: '[email protected]' });
actions.set({ name: 'Jane', email: '[email protected]' });
actions.resume(); // Resume history tracking
// Only the final state will be in history
};
return (
<div>
<input
value={state.present.name}
onChange={(e) => actions.set({ ...state.present, name: e.target.value })}
/>
<button onClick={handleBulkUpdate}>Bulk Update</button>
</div>
);
};useDistinct
Detects distinct changes in values with support for deep comparison and custom equality checks. Useful for tracking whether complex objects have actually changed.
import useDistinct from 'use-good-hooks/use-distinct';
const UserProfileForm = ({ user }) => {
const { distinct, value, prevValue } = useDistinct(user, { deep: true });
useEffect(() => {
if (distinct) {
console.log('User data changed from:', prevValue, 'to:', value);
// Perhaps save to backend or update UI
}
}, [distinct, prevValue, value]);
return (
<div>
<h2>Editing profile for: {user.name}</h2>
{distinct && <div className="alert">Unsaved changes!</div>}
{/* Form inputs */}
</div>
);
};Parameters
inputValue: The value to check for changesoptions: (Optional) Configuration options:deep: Boolean to enable deep equality comparison (default: false)compare: Custom comparison function (a, b) => booleandebounce: Debounce time in milliseconds (default: 0)
Returns
- Object with:
distinct: Boolean indicating if the value changedprevValue: The previous distinct valuevalue: The current value
useStorageState
Persists state to localStorage or sessionStorage with automatic serialization/deserialization.
import useStorageState from 'use-good-hooks/use-storage-state';
const ThemePreferences = () => {
const [preferences, setPreferences, { removeKey }] = useStorageState('theme-prefs', {
darkMode: false,
fontSize: 'medium',
compactView: true
});
return (
<div>
<h2>Theme Settings</h2>
<label>
<input
type="checkbox"
checked={preferences.darkMode}
onChange={() => setPreferences({...preferences, darkMode: !preferences.darkMode})}
/>
Dark Mode
</label>
{/* More settings */}
<button onClick={removeKey}>Reset to Defaults</button>
</div>
);
};Parameters
key: Storage key nameinitialState: Default state if no stored value existsoptions: (Optional) Configuration options:storage: 'local' or 'session' (default: 'local')debounce: Debounce time in milliseconds (default: 500)onError: Error callback functionomitKeys: Array of keys to omit from storage or function (value, key) => booleanpickKeys: Array of keys to include in storage or function (value, key) => boolean
Returns
- Array with:
- State value
- State setter function
- Object with utility functions:
removeKey: Function to clear the storage key
useUrlState
Synchronizes state with URL query parameters. Great for shareable UI states, filters, pagination, and search terms.
import useUrlState from 'use-good-hooks/use-url-state';
const ProductFilter = () => {
const [filters, setFilters] = useUrlState({
category: '',
minPrice: 0,
maxPrice: 1000,
sortBy: 'newest'
}, {
url: new URL(window.location.href),
kebabCase: true,
omitValues: ['', 0]
});
return (
<div>
<select
value={filters.category}
onChange={(e) => setFilters({...filters, category: e.target.value})}
>
<option value="">All Categories</option>
<option value="electronics">Electronics</option>
<option value="clothing">Clothing</option>
</select>
{/* More filter controls */}
</div>
);
};Parameters
initialState: Default state if URL has no parametersoptions: Configuration options:url: URL object to use (required)debounce: Debounce time in milliseconds (default: 500)kebabCase: Convert camelCase keys to kebab-case in URL (default: true)prefix: Optional prefix for URL parametersonError: Error callback functionomitKeys: Array of keys to omit from URL or function (value, key) => booleanpickKeys: Array of keys to include in URL or function (value, key) => booleanomitValues: Array of values to omit from URL or function (value, key) => boolean
Returns
- Array with:
- State value
- State setter function
useGlobalState and createGlobalState
Creates and manages global state that can be shared across components with automatic synchronization.
import { createGlobalState, useGlobalState } from 'use-good-hooks/use-global-state';
// Create a global state instance (typically in a separate file)
const counterState = createGlobalState({ count: 0 });
// Component A
const CounterDisplay = () => {
const [counter, setCounter] = useGlobalState(counterState);
return (
<div>
<p>Count: {counter.count}</p>
<button onClick={() => setCounter({ count: counter.count + 1 })}>
Increment
</button>
</div>
);
};
// Component B (in a different part of your app)
const CounterActions = () => {
const [counter, setCounter] = useGlobalState(counterState);
return (
<div>
<button onClick={() => setCounter({ count: 0 })}>
Reset Count
</button>
<button onClick={() => setCounter(prev => ({ count: prev.count + 5 }))}>
Add 5
</button>
</div>
);
};Usage
- First, create a global state store:
// state/counter.ts
import { createGlobalState } from 'use-good-hooks/use-global-state';
export const counterState = createGlobalState({ count: 0 });- Then use it in any component:
import { useGlobalState } from 'use-good-hooks/use-global-state';
import { counterState } from './state/counter';
const MyComponent = () => {
const [counter, setCounter] = useGlobalState(counterState);
// ...
};createGlobalState Parameters
initialState: The initial state value
createGlobalState Returns
- Array with:
state: The current statesetState: Function to update statestore: Object with utility methods:getState(): Function to get current statesubscribe(callback): Subscribe to state changesresetState(): Reset to initial state
useGlobalState Parameters
globalState: The global state created withcreateGlobalState
useGlobalState Returns
- Array with:
state: The component's local copy of the statesetState: Function to update global state (accepts new value or update function)
useTemporaryState
Creates a temporary state that resets after a specified timeout.
import useTemporaryState from 'use-good-hooks/use-temporary-state';
const [state, setState] = useTemporaryState('initial', 1000);
// State will reset to 'initial' after 1 secondParameters
initialState: The initial state valuems: The timeout duration in milliseconds (default: 3000)
Returns
- Array with:
state: The current statesetState: Function to update state
useLateState
Delays the update of a state value until a specified time has passed. This is useful for scenarios where you want to introduce a delay before a state change takes effect, such as showing a loading spinner for a minimum amount of time.
import useLateState from 'use-good-hooks/use-late-state';
const DelayedComponent = () => {
const [status, setStatus, cancelUpdate] = useLateState('Waiting...', 2000); // 2-second delay
const handleUpdate = () => {
setStatus('Updated!');
};
const handleImmediateUpdate = () => {
setStatus('Immediately Updated!', true);
};
const handleCancel = () => {
const wasCancelled = cancelUpdate();
if (wasCancelled) {
alert('Update cancelled!');
}
};
return (
<div>
<p>Status: {status}</p>
<button onClick={handleUpdate}>Update after 2s</button>
<button onClick={handleImmediateUpdate}>Update Immediately</button>
<button onClick={handleCancel}>Cancel Update</button>
</div>
);
};Parameters
initial: The initial value of the state.delay: The delay in milliseconds before the state is updated.
Returns
value: The current state value.setLate: A function to update the state after the specified delay. It can also accept a second boolean argument to update the state immediately.cancel: A function to cancel a pending state update.
🧪 Running Tests
This library is thoroughly tested with Vitest and React Testing Library. To run the tests:
# Using npm
npm test
# Using yarn
yarn test
# Using pnpm
pnpm test🔄 How hooks update and optimize performance
Each hook in this library is designed with performance in mind:
useDebounceanduseThrottlereduce unnecessary renders using Lodash's optimized implementationsuseDistinctavoids reference equality problems with optional deep comparisonuseStorageStatebatches storage updates to reduce expensive serialization/deserializationuseUrlStateefficiently handles URL synchronization with debouncinguseGlobalStateandcreateGlobalStateprovide a way to share state across components with automatic synchronizationuseTemporaryStateallows for temporary state that resets after a timeoutuseLateStateprovides a mechanism to delay state updates, which can be useful for managing UI transitions and asynchronous operations.
🛠️ Development
# Install dependencies
yarn install
# Start development server
yarn dev
# Run tests
yarn test
# Build the library
yarn build
# Lint and format the code
yarn lint🙏 Acknowledgements
This project was built with:
📝 License
MIT © Felipe Rohde
👨💻 Author
Felipe Rohde
- Twitter: @felipe_rohde
- Github: @feliperohdee
- Email: [email protected]
