
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
react-hooksack
Advanced tools
A lightweight, fully typed store for react, based entirely on hooks.
Keep re-rendering of involved components at a minimum. See this codesandbox with nested components. (thanks to @andrewgreenh for creating #3)
npm install --save react-hooksack
# or
yarn add react-hooksack
import makeStore from 'react-hooksack';
const useStore = makeStore(initialValue, reducer);
| object | required | type | description |
|---|---|---|---|
initialValue | yes | State | Whatever you pass as initial value becomes the store`s state type (we will refer asState).makeStoreis type generic, so you could set more complex types like this:makeStore<null | number>(null) |
reducer | no | ( | If passed, setState will only accept ReducerAction later. |
// get state and setter
const [state, setState] = useStore();
// or just get state
const state = useStore('justState');
// or just get setter (this one avoid re-renders)
const setState = useStore('justSetter');
| object | type | required | description |
|---|---|---|---|
useStore | ( | Without arguments, useStore behaves like useState of React (except for the initial value). Otherwise you get what you asked for (see below). | |
state | State | Will be of the type you set as initialValue on makeStore. Every component that consumes state will re-render whenever state changes. | |
setState (no reducer) | ( | Hooksack is using React`s useState hook under the hood. Thus, you can just pass the new state of type State or pass a function that gets the current state and has to return the new state. | |
setState (reducer) | ( | If makeStore was initialized with a reducer, setState expects that reducer`s ReducerAction as argument. | |
just | 'justState' | no | useStore will just return the current state of type State. |
just | 'justSetter' | no | useStore will just return the state setter setState depending on if you passed a reducer to makeStore or not (see above). Components that just get the state setter through literal 'justSetter' will not get re-rendered whenever state changes. |
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import makeStore from 'react-hooksack';
// util for counting rerenders
import { LogTable, useLogStore } from './logRender';
// make a new store and set it's initial value (to 0)
const useClickStore = makeStore(0);
// a component that subscribes to the current state of "clicks"
// und uses the setter "setClicks"
const ViewAndUpdate = () => {
// by consuming the state "clicks" this component will rerender
// every time, "clicks" gets updated
const [clicks, setClicks] = useClickStore();
// just to count rerenderings
const logRender = useLogStore('justSetter');
React.useEffect(() => logRender('ViewAndUpdate'));
return (
<button
title="ViewAndUpdate"
onClick={() => setClicks(currentClicks => currentClicks + 1)}
>
add click (currently {clicks})
</button>
);
};
// a simple component to view the store's state
const ViewOnly = () => {
// just subsribing to the state of "clicks" - no setter required here
// this component will rerender with every update of "clicks" too
const clicks = useClickStore('justState');
// just to count rerenderings
const logRender = useLogStore('justSetter');
React.useEffect(() => logRender('ViewOnly'));
return (
<div title="ViewOnly">
<span>clicks: {clicks}</span>
</div>
);
};
// a component that will only set the new state for "clicks"
const UpdateOnly = () => {
// by just using the setter for "clicks" this component will not
// rerender every time "clicks" updates
const setClicks = useClickStore('justSetter');
// just to count rerenderings
const logRender = useLogStore('justSetter');
React.useEffect(() => logRender('UpdateOnly'));
return (
<button title="UpdateOnly" onClick={() => setClicks(clicks => clicks + 1)}>
add click
</button>
);
};
const App = () => (
<div>
<ViewOnly />
<ViewAndUpdate />
<UpdateOnly />
<hr />
<LogTable />
</div>
);
ReactDOM.render(<App />, document.getElementById('root'));
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import makeStore from 'react-hooksack';
// shape a ToDo
interface Todo {
id: number;
name: string;
done: boolean;
}
// define the ToDo store reducer
const reducer = (
state: Todo[],
action: { type: 'add' | 'del' | 'toggle'; todo: Todo },
) => {
switch (action.type) {
case 'add':
// ensure to always return a new object
return [...state, action.todo];
case 'del':
// ensure to always return a new object
return state.filter(todo => todo !== action.todo);
case 'toggle':
// ensure to always return a new object
return state.map(todo =>
todo === action.todo ? { ...todo, done: !todo.done } : todo,
);
default:
throw new Error();
}
};
// make a new store, set it's initial value and pass reducer
const useTodoStore = makeStore(
[
{ id: 1, done: false, name: 'remember the milk' },
{ id: 2, done: false, name: 'feed the cat' },
{ id: 3, done: true, name: 'walk the dog' },
{ id: 4, done: true, name: "order a table at Luigi's" },
],
reducer,
);
// component to render and toggle a Todo
const Todo: React.FC<{ todo: Todo }> = ({ todo }) => {
const setTodos = useTodoStore('justSetter');
const style = {
textDecoration: todo.done ? 'line-through' : undefined,
};
const handleChange = () => {
setTodos({ type: 'toggle', todo });
};
return (
<li>
<input type="checkbox" checked={todo.done} onChange={handleChange} />
<span style={style}>{todo.name}</span>
</li>
);
};
// component to render a list of Todos
const TodoList: React.FC<{ todos: Todo[] }> = ({ todos }) => (
<ul>
{todos.map(todo => (
<Todo key={todo.id} todo={todo} />
))}
</ul>
);
// component to tell appart already done Todos from to be tone ones
const AllTodos = () => {
const todos = useTodoStore('justState');
const toBeDone = todos.filter(todo => todo.done === false);
const done = todos.filter(todo => todo.done);
return (
<div>
{toBeDone.length > 0 && (
<div>
<h1>to be done</h1>
<TodoList todos={toBeDone} />
</div>
)}
{done.length > 0 && (
<div>
<h1>already done</h1>
<TodoList todos={done} />
</div>
)}
</div>
);
};
// component to add a new, undone todo
const AddTodo = () => {
const dispatchTodos = useTodoStore('justSetter');
const todoInput = React.useRef<HTMLInputElement>(null);
const addTodo = () => {
if (todoInput && todoInput.current) {
const name = todoInput.current.value;
dispatchTodos({
type: 'add',
todo: { name, done: false, id: Date.now() },
});
todoInput.current.value = '';
}
};
return (
<div>
<input ref={todoInput} placeholder="new Todo name" />
<button onClick={addTodo}>add</button>
</div>
);
};
const App = () => (
<div>
<AllTodos />
<AddTodo />
</div>
);
ReactDOM.render(<App />, document.getElementById('root'));
npm run test
# or
npm run test:coverage
I got inspired by a blog post of Jhonny Michel. He also released react-hookstore but I:
FAQs
A lightweight, fully typed store for react, based entirely on hooks.
We found that react-hooksack 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.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.