1.1.0 • Published 8 months ago
@n0n3br/react-use-state-with-history v1.1.0
React useState with History
A custom React hook that enhances the standard useState hook by providing history tracking and navigation capabilities (undo/redo functionality).
Features
- Drop-in replacement for
useStatefor basic usage - Keeps a history of state changes
- Provides functions to navigate back (
back) and forward (forward) through the history - Provides functions to jump directly to the first (
first) and last (last) state in history - Provides a function to jump to a specific index (
go) in the history - Provides functions to clear history:
clear(clears all history, keeping current state),trimStart(clears history before current pointer), andtrimEnd(clears history after current pointer) - Exposes the complete history array and the current pointer position
- Works with any data type (numbers, strings, objects, arrays, etc.)
- Written in TypeScript for type safety
Installation
npm install @n0n3br/react-use-state-with-history
# or
yarn add @n0n3br/react-use-state-with-history
# or
pnpm add @n0n3br/react-use-state-with-historyBasic Usage
import { useStateWithHistory } from "@n0n3br/react-use-state-with-history";
function Counter() {
const [count, setCount, { back, forward }] = useStateWithHistory(0);
return (
<div>
<p>Current count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
<button onClick={back}>Undo</button>
<button onClick={forward}>Redo</button>
</div>
);
}Advanced Usage
import { useStateWithHistory } from "@n0n3br/react-use-state-with-history";
function TextEditor() {
const [
text,
setText,
{
history,
pointer,
back,
forward,
go,
first,
last,
clear,
trimStart,
trimEnd,
},
] = useStateWithHistory("");
return (
<div>
<textarea
value={text}
onChange={(e) => setText(e.target.value)}
rows={5}
cols={40}
/>
<div>
<button onClick={back} disabled={pointer <= 0}>
Undo
</button>
<button onClick={forward} disabled={pointer >= history.length - 1}>
Redo
</button>
<button onClick={first} disabled={pointer === 0}>
First Version
</button>
<button onClick={last} disabled={pointer === history.length - 1}>
Latest Version
</button>
<button onClick={clear} disabled={history.length <= 1}>
Clear History
</button>
<button onClick={trimStart} disabled={pointer === 0}>
Trim Start
</button>
<button onClick={trimEnd} disabled={pointer === history.length - 1}>
Trim End
</button>
</div>
<div>
<p>History States: {history.length}</p>
<p>Current Position: {pointer}</p>
{/* Display history entries */}
<div>
{history.map((item, index) => (
<button
key={index}
onClick={() => go(index)}
style={{
fontWeight: index === pointer ? "bold" : "normal",
margin: "2px",
}}
>
{index}
</button>
))}
</div>
</div>
</div>
);
}API Reference
useStateWithHistory
function useStateWithHistory<T>(initialValue: T): [
T,
(value: T | ((prevState: T) => T)) => void,
{
history: T[];
pointer: number;
back: () => void;
forward: () => void;
go: (index: number) => void;
first: () => void;
last: () => void;
clear: () => void;
trimStart: () => void;
trimEnd: () => void;
}
];Parameters
initialValue: T- The initial state value (can be any type)
Return Value
Returns a tuple with three elements:
- Current State (
T): The current state value - State Setter (
(value: T | ((prevState: T) => T)) => void): Function to update the state- Accepts a new value or a function that receives the previous state and returns a new value
- Each update adds a new entry to the history
- History Controls (Object): An object containing:
history: T[]- Array of all state values in historypointer: number- Current position in the history arrayback: () => void- Move to the previous state in historyforward: () => void- Move to the next state in historygo: (index: number) => void- Jump to a specific index in historyfirst: () => void- Jump to the first state in historylast: () => void- Jump to the most recent state in historyclear: () => void- Clears all history, keeping only the current state as the initial state. The pointer is reset to 0.trimStart: () => void- Clears all history entries before the current pointer. The current state becomes the first state in the new history, and the pointer is reset to 0.trimEnd: () => void- Clears all history entries after the current pointer. The pointer remains at its current position, which is now the last entry in the history.
Working with Complex Types
The hook works seamlessly with complex data types like objects and arrays:
import { useStateWithHistory } from "@n0n3br/react-use-state-with-history";
function UserForm() {
const [user, setUser, { back, forward }] = useStateWithHistory({
name: "",
email: "",
age: 0,
});
const updateField = (field, value) => {
setUser((prevUser) => ({
...prevUser,
[field]: value,
}));
};
return (
<div>
<div>
<label>Name:</label>
<input
value={user.name}
onChange={(e) => updateField("name", e.target.value)}
/>
</div>
<div>
<label>Email:</label>
<input
value={user.email}
onChange={(e) => updateField("email", e.target.value)}
/>
</div>
<div>
<label>Age:</label>
<input
type="number"
value={user.age}
onChange={(e) => updateField("age", parseInt(e.target.value) || 0)}
/>
</div>
<button onClick={back}>Undo</button>
<button onClick={forward}>Redo</button>
</div>
);
}License
MIT
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Publishing
To publish this package to npm under the @n0n3br scope:
# Login to npm (if not already logged in)
npm login
# Build the package
npm run build
# Publish to npm
npm publishNote: The package is configured with "access": "public" in package.json, which allows the scoped package to be publicly accessible.