@utilityjs/use-on-change
Version:
A React hook that invokes a callback anytime a value changes.
343 lines (272 loc) • 8.28 kB
Markdown
<div align="center">
# UtilityJS | useOnChange
A React hook that invokes a callback anytime a value changes.
</div>
<hr />
## Features
- **Change Detection**: Automatically detects when a value changes
- **Callback Stability**: Uses stable callback references to prevent unnecessary
re-renders
- **Type Safety**: Full TypeScript support with generic typing
- **Performance Optimized**: Efficient comparison using previous value tracking
- **Side Effect Management**: Perfect for triggering side effects on value
changes
## Installation
```bash
npm install @utilityjs/use-on-change
```
or
```bash
pnpm add @utilityjs/use-on-change
```
## Usage
### Basic Usage
```tsx
import { useOnChange } from "@utilityjs/use-on-change";
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
useOnChange(count, newCount => {
console.log(`Count changed to: ${newCount}`);
});
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
```
### User Analytics
```tsx
import { useOnChange } from "@utilityjs/use-on-change";
import { useState } from "react";
function UserDashboard() {
const [currentPage, setCurrentPage] = useState("home");
useOnChange(currentPage, page => {
// Track page views
analytics.track("page_view", { page });
});
return (
<nav>
<button onClick={() => setCurrentPage("home")}>Home</button>
<button onClick={() => setCurrentPage("profile")}>Profile</button>
<button onClick={() => setCurrentPage("settings")}>Settings</button>
</nav>
);
}
```
### API Data Fetching
```tsx
import { useOnChange } from "@utilityjs/use-on-change";
import { useState, useEffect } from "react";
function UserProfile() {
const [userId, setUserId] = useState(1);
const [userData, setUserData] = useState(null);
const [loading, setLoading] = useState(false);
useOnChange(userId, async newUserId => {
setLoading(true);
try {
const response = await fetch(`/api/users/${newUserId}`);
const data = await response.json();
setUserData(data);
} catch (error) {
console.error("Failed to fetch user:", error);
} finally {
setLoading(false);
}
});
return (
<div>
<select
value={userId}
onChange={e => setUserId(Number(e.target.value))}
>
<option value={1}>User 1</option>
<option value={2}>User 2</option>
<option value={3}>User 3</option>
</select>
{loading ? (
<p>Loading...</p>
) : userData ? (
<div>
<h2>{userData.name}</h2>
<p>{userData.email}</p>
</div>
) : null}
</div>
);
}
```
### Form Validation
```tsx
import { useOnChange } from "@utilityjs/use-on-change";
import { useState } from "react";
function EmailForm() {
const [email, setEmail] = useState("");
const [isValid, setIsValid] = useState(true);
const [errorMessage, setErrorMessage] = useState("");
useOnChange(email, newEmail => {
if (newEmail === "") {
setIsValid(true);
setErrorMessage("");
return;
}
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
const valid = emailRegex.test(newEmail);
setIsValid(valid);
setErrorMessage(valid ? "" : "Please enter a valid email address");
});
return (
<div>
<input
type="email"
value={email}
onChange={e => setEmail(e.target.value)}
placeholder="Enter your email"
style={{
borderColor: isValid ? "#ccc" : "#ff0000",
}}
/>
{!isValid && <p style={{ color: "#ff0000" }}>{errorMessage}</p>}
</div>
);
}
```
### Local Storage Sync
```tsx
import { useOnChange } from "@utilityjs/use-on-change";
import { useState, useEffect } from "react";
function Settings() {
const [theme, setTheme] = useState("light");
const [language, setLanguage] = useState("en");
// Load from localStorage on mount
useEffect(() => {
const savedTheme = localStorage.getItem("theme");
const savedLanguage = localStorage.getItem("language");
if (savedTheme) setTheme(savedTheme);
if (savedLanguage) setLanguage(savedLanguage);
}, []);
// Save to localStorage when values change
useOnChange(theme, newTheme => {
localStorage.setItem("theme", newTheme);
document.body.className = `theme-${newTheme}`;
});
useOnChange(language, newLanguage => {
localStorage.setItem("language", newLanguage);
// Update i18n or trigger language change
});
return (
<div>
<div>
<label>Theme:</label>
<select
value={theme}
onChange={e => setTheme(e.target.value)}
>
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
</div>
<div>
<label>Language:</label>
<select
value={language}
onChange={e => setLanguage(e.target.value)}
>
<option value="en">English</option>
<option value="es">Spanish</option>
<option value="fr">French</option>
</select>
</div>
</div>
);
}
```
### Complex Object Changes
```tsx
import { useOnChange } from "@utilityjs/use-on-change";
import { useState } from "react";
type FilterState = {
category: string;
priceRange: [number, number];
sortBy: string;
};
function ProductFilter() {
const [filters, setFilters] = useState<FilterState>({
category: "all",
priceRange: [0, 1000],
sortBy: "name",
});
useOnChange(filters, newFilters => {
console.log("Filters changed:", newFilters);
// Trigger API call to fetch filtered products
fetchProducts(newFilters);
});
const updateFilter = (key: keyof FilterState, value: any) => {
setFilters(prev => ({ ...prev, [key]: value }));
};
return (
<div>
<select
value={filters.category}
onChange={e => updateFilter("category", e.target.value)}
>
<option value="all">All Categories</option>
<option value="electronics">Electronics</option>
<option value="clothing">Clothing</option>
</select>
<input
type="range"
min="0"
max="1000"
value={filters.priceRange[1]}
onChange={e => updateFilter("priceRange", [0, Number(e.target.value)])}
/>
<select
value={filters.sortBy}
onChange={e => updateFilter("sortBy", e.target.value)}
>
<option value="name">Name</option>
<option value="price">Price</option>
<option value="rating">Rating</option>
</select>
</div>
);
}
```
## API
### `useOnChange<T>(value, onChange)`
A React hook that monitors a value and executes a callback when it changes.
#### Parameters
- `value: T` - The value to monitor for changes
- `onChange: (current: T) => void` - Callback function invoked when the value
changes
#### Returns
- `void` - This hook doesn't return anything
#### Behavior
- **Change Detection**: Uses strict equality (`!==`) to detect changes
- **Callback Timing**: The callback is invoked after the component re-renders
with the new value
- **Initial Render**: The callback is not invoked on the initial render, only on
subsequent changes
- **Stable References**: Uses `useGetLatest` to ensure the callback reference is
always current
### Dependencies
This hook depends on:
- `@utilityjs/use-previous-value` - For tracking previous values
## Use Cases
- **Analytics Tracking**: Track user interactions and state changes
- **API Calls**: Trigger data fetching when parameters change
- **Form Validation**: Validate fields as users type
- **Local Storage**: Sync state changes to localStorage
- **Side Effects**: Execute any side effect in response to value changes
- **Logging**: Log state changes for debugging purposes
## Contributing
Read the
[contributing guide](https://github.com/mimshins/utilityjs/blob/main/CONTRIBUTING.md)
to learn about our development process, how to propose bug fixes and
improvements, and how to build and test your changes.
## License
This project is licensed under the terms of the
[MIT license](https://github.com/mimshins/utilityjs/blob/main/LICENSE).