
Product
Introducing Webhook Events for Alert Changes
Add real-time Socket webhook events to your workflows to automatically receive software supply chain alert changes in real time.
react-hookstate-initial
Advanced tools
Plugin for react-hookstate to enable access to initial state value and to modified status.
Tiny, type-safe, feature-rich useState-like React hook to manage complex state (objects, arrays, nested data, forms) and global stores (alternative to Redux/Mobx)
API guide table of contents:
useState for arraysuseState for objectsuseState for complex data
useState for complex data stored globally
Array state - useState for arrays (read more...):
import { useStateArray } from 'react-use-state-x';
const UseStateArrayExample = () => {
// there are other actions available in addition to push and remove
const [array, { push, remove }] = useStateArray([1, 2]);
return (
<div>
{array.map((elem, index) =>
<span>
Element {elem} (<button onClick={() => remove(index)}>Remove</button>)
</span>
)}
<button onClick={() => push(array.length)}>Add</button>
</div>
);
};
Object state - useState for objects (read more...):
import { useStateObject } from 'react-use-state-x';
const UseStateObjectExample = () => {
// there are other actions available in addition to merge and remove
const [instance, { merge, update }] = useStateObject({ a: 1, b: 'two', c: false });
return (
<div>
Current object state: {JSON.stringify(instance)}
<button onClick={() => update('a', 2)}>Update A field</button>
<button onClick={() => merge({ b: 'Three', c: true })}>Update B and C fields</button>
</div>
);
};
Complex state and Form state - useState for complex data and two-way form data binding via valuelink pattern (read more...):
import { useStateLink } from 'react-use-state-x';
const UseStateLinkExample = () => {
const stateLink = useStateLink({
title: 'Code Complete',
popularity: 1
}, {
targetHooks: {
popularity: {
__validate: (v) => !Number.isFinite(v) ? 'Popularity should be a number' : undefined
}
}
});
const links = stateLink.nested; // obtain valuelinks to nested fields
return (
<div>
<label>Title</label>
<input value={links.title.value} onChange={e => links.title.set(e.target.value)} />
<label>Popularity</label>
<input value={links.popularity.value} onChange={e => links.popularity.set(Number(e.target.value))} />
{stateLink.valid ? 'The input is valid' : stateLink.errors.join(',')}
</div>
);
};
Global state and Global state reducer - useState for complex data stored globally wrapped by strict type-safe action reducer API (alternative to Redux/Mobx, read more...):
//
// file: Store.tsx - implementation of type-safe, strict API for global store
//
import { useStateLink, createStateLink } from 'react-use-state-link';
export interface Book {
title: string;
popularity: number;
}
const Store = createStateLink<Book[]>([{
title: 'Code complete',
popularity: 1,
}]);
export const StoreObserver = Store.Observer;
export function useStore() {
const link = useStateLink(Store);
return {
addBook(book: Book) {
link.inferred.push(book);
},
updateTitle(bookIndex: number, title: string) {
link.nested[bookIndex].nested.title.set(title);
},
getBooks(): Book[] {
return link.value;
}
};
}
//
// file: OtherComponent.tsx - demonstrates how to use the global store in a component
//
import { useStore } from './Store'
const UseGlobalStoreExample = () => {
const store = useStore();
return (
<div>
{store.getBooks().map((book, index) =>
// show the title in the editable input box for each book
<input key={index} value={book.title} onChange={e => store.updateTitle(index, e.target.value)} />
)}
<button onClick={() => store.addBook({ title: 'Code complete', popularity: 1 })} >
Add another book
</button>
</div>
);
};
//
// file: App.tsx - activates the usage of the global store for the entire app
//
import { StoreObserver } from './Store';
const App = () => {
return (
<StoreObserver>
{
// nested components, which use the Store will re-render
// when the Store state is changed
}
<UseGlobalStoreExample />
</StoreObserver>
);
};
React.useContext and React.useStateUse github ticketing system to ask questions, report bugs or request features. Feel free to submit pull requests.
Using NPM:
npm install --save react-use-state-x
Using yarn:
yarn add react-use-state-x
useStateArray returns the current state of an array instance and a set of functions to mutate the state of the array in various ways. The following example demonstrates the usage of push mutation action, which adds one more element in to the array.
const UseStateArrayExample = () => {
const [array, { push }] = useStateArray([1, 2]);
return (
<div>
{array.join(',')}
<button onClick={() => push(array.length)}>Add</button>
</div>
);
};
There the following array mutation actions available:
set([...]) or set((prevState) => [...]) sets new value of the array state. It has got the same behaviour as the second value returned from the React.useState function
merge({...}) or merge((prevState) => ({...})) sets new value of the array state, updating the provided elements of the array, for example:
merge({
0: 'the first element is updated',
4: 'and the fifth too',
})
Note: prevState variable in the callback is a clone/copy of the current array state
update(index, newElementValue) or update(index, (prevElementValue) => newElementValue) sets new value of the array state, updating the element of an array by the specified index
concat([...]) or concat((prevState) => [...]) sets new value of the array state, appending the provided array to the end of the current array.
Note: prevState variable in the callback is a clone/copy of the current array state
push(newElement) sets new value of the array state, adding new element to the end
pop() sets new value of the array state, removing the last element
insert(indexWhereToInsert, newElement) sets new value of the array state, inserting the new element by the specified index
remove(index) sets new value of the array state, removing the element by the specified index
swap(index1, index2) sets new value of the array state, swapping two elements by the specified indexes
useStateObject returns the current state of an object instance and a set of functions to mutate the state of the object in various ways. The following example demonstrates the usage of merge mutation action, which updates the specified properties of the object.
const UseStateObjectExample = () => {
const [instance, { merge }] = useStateObject({ a: 1, b: 'two' });
return (
<div>
{JSON.stringify(instance)}
<button onClick={() => merge({ b: 'Three' })}>Modify instance</button>
</div>
);
};
There the following object mutation actions available:
set([...]) or set((prevState) => [...]) sets new value of the object state. It has got the same behaviour as the second value returned from the React.useState functionmerge({...}) or merge((prevState) => ({...})) sets new value of the object state, updating the specified propertiesupdate(propertyKey, newPropertyValue) or update(propertyKey, (prevPropertyValue) => newPropertyValue) sets new value of the object state, updating the specified propertyWhen the state data contains a mix of nested objects, arrays and primitive variables of different types, useStateArray or useStateObject are not sufficient anymore. We need something what can provide set-state-like actions for nested data and something what is aware of the types of these nested objects and arrays.
useStateLink is used in this case. For example:
interface Book {
title: string;
popularity: number;
authors: string[];
}
interface Catalog {
books: Book[];
lastUpdated: Date;
}
const UseStateLinkExample = () => {
// type annotation is for documentation purposes,
// it is inferred by the compiler automatically
const link: ValueLink<Catalog> = useStateLink({
books: [
{
title: 'Code Complete',
popularity: 1,
authors: ['Steve McConnell']
}
],
lastUpdated: new Date()
} as Catalog);
...
};
The link variable is of type ValueLink<Catalog>, which has got two fundamental properties:
value - returns the instance of data, of Catalog type in this exampleset(...) or set((prevState) => ...) - function which allows to set new value state, of Catalog type in this example, similarly to the setState variable returned by React.useState hook.The set function will not accept partial updates. Object state mutation actions, like merge, update, etc. are available via inferred property. For example:
link.inferred.update('lastUpdated', new Date());
Because it is all typescript compiler checked, the first key for update can be only the names of the properties of the type of value, Catalog type in our example.
Updating the nested property, will cause the update of the original state, representated by link.value instance.
However, there is better way to update the nested fields:
link.nested.lastUpdated.set(new Date());
nested 'converts' a valuelink of an object to an object of nested value links. link.nested object will contain the same keys as the value object. These properties will be of type ValueLink<T> - nested value links to manage the state of nested fields. In the example above, we set the value of ValueLink<Date> link. Because it is all typescript compiler checked, it can only be a value of type Date.
Similarly, we can obtain the link to the books array:
// type annotation is for documentation purposes,
// it is inferred by the compiler automatically
const booksLink: ValueLink<Book[]> = link.nested.books;
It is nested valuelink of an array value. It has got value and set properties. Array mutation actions, like push, pop, insert, etc. can be accessed via inferred property. For example:
booksLink.inferred.push({
title: 'Rapid Development',
popularity: 2,
authors: ['Steve McConnell']
});
Again, because it is typescript compiler checked, push will require the argument of compatible type, Book in the example.
Similary to object valuelink, array valuelink has got nested property, which 'converts' value link of an array to an array of nested valuelinks. For example:
// type annotation is for documentation purposes,
// it is inferred by the compiler automatically
const firstBookLink: ValueLink<Book> = booksLink.nested[0];
firstBookLink is valuelink of an object. Similarly to updating the property on the root object, we can update the property on the nested object:
firstBookLink.nested.popularity.set(prevValue => prevValue + 1);
ValueLink's path property captures 'Javascript' object 'path' to an element relative to the root object. For example:
link.path === []
link.nested.books.path === ["books"]
link.nested.books.nested[0].path === ["books", 0]
link.nested.books.nested[0].nested.title.path === ["books", 0, "title"]
I hope, this example demonstrates how it is possible to traverse the complex data and manage deeply nested properties.
Form state is frequently referred as two-way data binding. Valuelink pattern is perfect way to achieve it. Because useStateLink returns ValueLink it can be used straight away to manage form state. Let's continue the above example:
const BookEditorExample = (props: { book: Book }) => {
// note we obtain nested links straight away
const links = useStateLink(props.book).nested;
return (
<div>
<label>Title</label>
<input
value={links.title.value}
onChange={e => links.title.set(e.target.value)}
/>
<label>Popularity</label>
<input
value={links.popularity.value}
onChange={e => links.popularity.set(Number(e.target.value))}
/>
</div>
);
};
The nested link to edit a book, could even come from the parent component, i.e. all down from the top component, which uses useStateLink hook.
const BookEditorExample = (props: { link: ValueLink<Book> }) => {
const links = link.nested;
...
};
Since we integrated the valuelink with the form state and user input, it would be good to have some data validation logic put in place.
useStateLink allows to define data validation rules. The rules can be attached to entire root level objects or deeply nested fields. The structure of validation rules is type checked by typescript compiler, i.e. it will not allow rules for unknown properties, for example.
Let's expand the above example, adding some validation logic:
const link = useStateLink<Catalog>({
books: [
{
title: 'Code complete',
popularity: 1,
authors: ['Steve McConnell']
}
],
lastUpdated: new Date()
}, {
targetHooks: {
books: {
__validate: v => v.length === 0 ? 'There should be at least one book' : undefined,
// wild-card hooks, applied to all elements of an array,
// which do not have specificaly targeted hooks
'*': {
title: {
__validate: v => v.length === 0 ? 'There should be non-empty book title' : undefined
},
popularity: {
__validate: v => !Number.isFinite(v) ? 'Popularity should be a number' : undefined
}
},
// hooks specifically targeting the first element of an array
0: { }
}
}
});
We provide the seconds argument of type Settings<Catalog>, which contains some __validation hooks. Validation hooks should return error message (or array of error messages, i.e. string[]) in case of validation failure. And undefined otherwise. These hooks are invoked by valuelink (or it's nested children valuelinks) when one of the following ValueLink's properties is accessed:
errors returns the array of all errors captured by validation. For example:
const errorMessage = link.errors.firstPartial().message || 'Form data is valid';
valid (invalid) returns true (false) if there are no errors. It can be used to prevent form submit action:
const submitButton = <button disabled={link.invalid}>Submit</button>;
ValueLink properties modified and unmodified allow to check if the current value state is different to the initial state. It can be used to detect what parts of data have been touched, eg. in form input. For example:
// true if any of nested fields have been modified
link.modified;
// true if any of books have been modified
link.nested.books.modified;
// true if any property of the first book has been modified
link.nested.books.nested[0].modified;
// true if title of the first book has been modified
link.nested.books.nested[0].nested.title.modified;
The initialValue property of ValueLink returns the initial state value.
The default comparison operator checks for structural identity. It is effectively the same as JSON.stringify(link.initialValue) === JSON.stringify(link.value). The comparison operator can be modified or extended using valuelink settings, similarly to validation settings. For example:
const link = useStateLink<Catalog>({
books: [
{
title: 'Code complete',
popularity: 1,
authors: ['Steve McConnell']
}
],
lastUpdated: new Date()
}, {
targetHooks: {
__compare: (current, initial, l) => current.lastUpdated === (initial && initial.lastUpdated),
}
});
If __compare hook returns undefined, default comparison operator is used.
In addition to __validate and __compare hooks, there is __preset hook. It can be used to reject invalid input, alter the mutation actions or for debugging purposes.
For example, let's say our Book's popularity input field should allow only digits:
const BookPopularityEditorExample = () => {
const valuelink = useStateLink(0, {
targetHooks: {
// the hook returns new value if number,
// and the old value, otherwise
// so, typing characters in the input field below
// makes no effect
__preset: (v: number, l: ReadonlyValueLink<number>) => Number.isFinite(v) ? v : l.value
}
});
return (
<div>
<label>Popularity</label>
<input
value={valuelink.value}
// Number() return NaN in case of bad input
onChange={e => valuelink.set(Number(e.target.value))}
/>
</div>
);
};
Let's say we also would like to update lastUpdated property automatically every time any change is made to our catalog:
const link = useStateLink<Catalog>({
books: [
{
title: 'Code complete',
popularity: 1,
authors: ['Steve McConnell']
}
],
lastUpdated: new Date()
}, {
targetHooks: {
__preset: v => ({ lastUpdated: new Date(), ...v })
}
});
Preset hook is useful for debugging sometimes:
const link = useStateLink<Catalog>({
books: [
{
title: 'Code complete',
popularity: 1,
authors: ['Steve McConnell']
}
],
lastUpdated: new Date()
}, {
targetHooks: {
__preset: v => {
console.log('Catalog is updated', v)
return v;
},
books: {
__preset: v => {
console.log('Books are updated', v)
return v;
}
}
}
});
If we would like to put preset hook for every property, we can use globalHooks instead of targetHooks:
const link = useStateLink<Catalog>({
books: [
{
title: 'Code complete',
popularity: 1,
authors: ['Steve McConnell']
}
],
lastUpdated: new Date()
}, {
globalHooks: {
__preset: (v, l) => {
console.log(`Value by path ${l.path} is set to ${v}`);
return v;
}
},
});
One of the problems with massive objects participating in React state lifecycle is performance of rendering of all of the components relying on the state data. This problem is not specific to this library, it is true in general: frequent massive data updates are hard in performance.
Returning to the Catalog example above, an application could render views / edit-forms for all books from the single complex state of the Catalog. It could use form state per every book to allow editing capabilities for the catalog. If a user decides to update a title of a single book, it would cause re-rendering for entire catalog on every key stroke, because update of nested data results in the update of the root state. The solution to this problem is to use temporary local to form component state, while capturing user's input, and update parent's state once editing is completed. For example:
const BookEditorExample = (props: { link: ValueLink<Book> }) => {
// we create local per-component state from the property supplied by the parent
const link = useStateLink(props.link.value);
return (
// when user leaves the editor, update the parent automatically
<div onBlur={() => props.link.set(link.value)}>
<label>Title</label>
<input
// use local state to manage user's input
value={link.nested.title.value}
onChange={e => link.nested.title.set(e.target.value)}
/>
<label>Popularity</label>
<input
// use local state to manage user's input
value={link.nested.popularity.value}
onChange={e => link.nested.popularity.set(Number(e.target.value))}
/>
<button
disabled={props.link.modifed}
// update parent's state once finished
onClick={() => props.link.set(link.value)}
>
Save
</button>
</div>
);
};
This improves the performance a lot, but does not allow us to reuse validation rules defined for the parent's valuelink. Let's modify the example and inherit validation rules and other hooks from the parent's link:
const BookEditorExample = (props: { link: ValueLink<Book> }) => {
// notice we dropped .value from props.link.value as an input for the initial local state
const link = useStateLink(props.link);
// the rest is the same
...
};
The created local link inherited validation rules from the parent link.
Plus, there are two more bonuses:
valid and modified status becomes different between parent link and local link. This allows parent components to react on valid and modified status changes as soon as they happen (i.e. while user types in the form.)Previously we relied on React to store the state per component. When multiple components need to use the shared state, which can be initialized prior to mounting of React components, we need to create global state store. Traditionally it was done using libraries like Redux or Mobx. This library allow to achieve the same but with simpler API and cleaner code.
Let's create the global store (we continue using the same example as above):
const GlobalStore = createStateLink(
// supply the initial value for the store
// it can be read from localStore or can be set to a default
// until the data is fetched from a server, for example
{
books: [
{
title: 'Code complete',
popularity: 1,
authors: ['Steve McConnell']
}
],
lastUpdated: new Date()
} as Catalog);
createStateLink has got the second argument which allows to specify validation rules, preset hooks, and other settings as for useStateLink.
Now, create a component which uses the store state (reads and updates):
const UseStateLinkExample = () => {
const link = useStateLink(GlobalStore);
return (
<div>
{JSON.stringify(link.value)}
<button
onClick={() => link.nested.books.inferred.push({
title: 'Code complete',
popularity: 1,
authors: ['Steve McConnell']
})}
>
Add the second book
</button>
</div>
);
};
And all of the components, which use global store link in the rendering logic, should be nested within the observer.
const App = () => {
return (
<GlobalStore.Observer>
{
// nested components, which use GlobalStore will re-render
// when GlobalStore state is changed
}
...
</GlobalStore.Observer>
);
};
One observer per application is enough, but you can specify few to provide fine-grained observation and improve the performance by doing so.
Valuelink API might not be the best to expose to consuming components. We can provide Mobx-like store functionality with clear type-safe API. For example:
// assuming it is module's private variable
const GlobalStore = createStateLink({ books: [], lastUpdated: new Date } as Catalog);
// export the observer component
export const StoreObserver = GlobalStore.Observer;
// export the hook to the store
export function useStore() {
const link = useStateLink(GlobalStore);
return {
addBook(book: Book) {
link.nested.books.inferred.push(book);
},
updateTitle(bookIndex: number, title: string) {
link.nested.books.nested[bookIndex].nested.title.set(title);
},
getBooks() {
return link.value.books;
}
};
}
Now, this store can be used as the following:
const UseStoreExample = () => {
const store = useStore();
return (
<div>
{JSON.stringify(store.getBooks())}
<button
onClick={() => store.addBook({
title: 'Code complete',
popularity: 1,
authors: ['Steve McConnell']
})}
>
Add the second book
</button>
</div>
);
};
Of course, you can create many independent stores per application.
Same result as using Redux or Mobx, but with cleaner & fewer lines of code, using smaller library overall, which handles all state lifecycle management scenarios.
Hope you enjoy using it. Feel free to contact me via github tickets or contribute to the library.
Write unit tests. The library is tested severely in scope of other projects, as some of these seriously rely on this library. However, it should not be an excuse not to write unit tests for this library. It has not been done because of lack of time.
useList and useMap libraries for local state management of arrays and objectsMIT
FAQs
Plugin for react-hookstate to enable access to initial state value and to modified status.
We found that react-hookstate-initial demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Product
Add real-time Socket webhook events to your workflows to automatically receive software supply chain alert changes in real time.

Security News
ENISA has become a CVE Program Root, giving the EU a central authority for coordinating vulnerability reporting, disclosure, and cross-border response.

Product
Socket now scans OpenVSX extensions, giving teams early detection of risky behaviors, hidden capabilities, and supply chain threats in developer tools.