React Localux is comfortable solution for separation store-related logic from react components. Unlike Redux main goal for React Localux is being home for compact local stores of smart components. Based on useReducer
and new Context API.
For example, you might have screen-like component of some item with vast logic related to this screen and it is required to support several different stacked screens of such items. Implementing such feature with global Redux store result complicated code, but it turns out that using single local store for each screen produces quite straightforward solution.
React 16.8+ only, because it uses hooks (useReducer mainly). For React < 16.8 see v1.
- Compact creation of store: just declare state, methods (combination of reducers and actions) and effects
- Thunk-like actions support — effects
- Default state support (handy for unit-testing)
- Redux dev tools logging support
- TypeScript first! Typings for all methods and effects for free.
- API based on hooks
- Lightweight: 1.7 Kb minified, 819 b gzip
Example code (from example.tsx)
npm i react-localux constate
// example-store.ts
import { createUseStore } from "react-localux";
const pause = async (timeout: number): Promise<any> =>
new Promise(resolve => setTimeout(resolve, timeout));
type State = {
loading: boolean;
data?: string;
error?: boolean;
};
export const defaultState: State = {
loading: false
};
export const useItemsStore = createUseStore(
defaultState,
{
loading: () => () => ({
loading: true
}),
loadSuccess: state => (data: string) => ({
...state,
loading: false,
data
}),
loadFailed: state => () => ({
...state,
loading: false,
error: true
})
},
{
loadItem: (_, methods) => async () => {
methods.loading();
// Pretend making API call which can fail
await pause(1000);
if (Math.random() > 0.5) {
methods.loadSuccess("Hooray!😁");
} else {
methods.loadFailed();
}
}
}
);
// exampleComponent.tsx
function ItemScreen() {
const { Provider } = useItemsStore;
return (
<Provider initialState={defaultState}>
<Item />
</Provider>
);
}
function Item() {
const { state, effects } = useItemsStore();
return (
<div>
<h1>Item Screen</h1>
{state.loading && <p>Loading...</p>}
{state.error && <p>Error loading 😕</p>}
{state.data && <p>Data loaded 🎆: {state.data}</p>}
<button onClick={effects.loadItem}>Load item</button>
</div>
);
}
/**
* If you need just slice of state and do not want re-renders
* on other state parts change, you can do such optimization with useMemo
**/
function ItemOptimized() {
const { state } = useItemsStore();
return useMemo(() => (
<div>
<h1>Item</h1>
{state.data
? <p>{`Data loaded 🎆: ${state.data}`</p>
: <p>Loading...</p>
}
</div>
), [state.data]);
}
Also see tests.
- No TypeScript support and due to API design decision for actions it is not possible to make types
- No async actions support
- Not very performant code on store creation
Alveron is really good lib with good documentation:
- No TypeScript support and due to API design decision for actions it is not possible to make types (looks like it is possible now with TS 3.7)
- No redux dev tools logging
- No async actions support built-in
- No redux dev tools logging
- API based on ambiguous immer (immer adds 4.3 Kb gzip). Immer adds performance penality on every action call, up to 15x on browsers without Proxy and 2x-3x on others.
- It is more "put any hook inside context" then complete store solution.
- No default state support, which is usefull for unit-testing.
That's why this library has been born. 👭
Thanks to @viventus for helpfull discussions.
Thanks to Constate library for simple solution for placing hooks inside context.