redonk
v4.0.6
Published
π Ridiculously simple state management in pure React
Downloads
1
Readme
π Ridiculously simple state management in pure React
Example demo with Counter and Todos
Installation
npm install redonk
yarn add redonk
Highlights
- Pure React state and context
- Light - around 760B in production
- Can be used for both global state and simpler cases when you just need to "lift up" your state
- Solves performance issues of context by creating multiple contexts and passing slices of state to them
- Full Typescript support
Usage
- Create the store
// store.jsx
export const {
Provider,
useSliceState,
useActions,
useRedonkState,
} = createStore({
slices: {
count: 0,
todos: {
items: [{ id: '1', text: 'Learn React', isDone: false }],
},
},
reducers: {
increment: state => {
return {
...state,
count: state.count + 1,
};
},
decrement: state => {
return {
...state,
count: state.count - 1,
};
},
addTodo: (state, todo) => {
return {
...state,
todos: {
...state.todos,
items: [...state.todos.items, todo],
},
};
},
},
});
- Wrap your component tree with the returned Provider
// app.jsx
import { Provider } from './store';
import { Counter } from './Counter';
import { Todos } from './Todos';
const App = () => {
return (
<Provider>
<Counter />
<Todos />
</Provider>
);
};
- And just use your state, actions and computed fields !!! π₯³
// counter.jsx
import {useSliceState, useActions } from "./store"
export const Counter = () => {
const count = useSliceState("count")
const {increment, decrement} = useActions()
return (
<div>
<button onClick={decrement}>-</button>
<div>{count}</count>
<button onClick={increment}>+</button>
</div>
)
}
// todos.jsx
import {useSliceState, useActions} from "./store"
export const Todos = () => {
const {items: todos} = useSliceState("todos")
const {addTodo} = useActions()
return (
<div>
<ul>
{todos.map(todo => (
<li key={todo.id}>
{todo.text}
{todo.isDone && "β
"}
</li>
))}
</ul>
<button onClick={() =>
addTodo({
id: Date.now().toString(),
text: "New todo",
isDone: false
})}>
Add todo
</button>
</div>
)
}
API
The library only exports one function!
createStore({ slices, reducers }) => { Provider, useSliceState, useActions, useRedonkState }
import { createStore } from 'redonk';
const { Provider, useSliceState, useActions, useRedonkState } = createStore({
slices: {
count: 0,
},
reducers: {
increment: state => {
return {
...state,
count: state.count + 1,
};
},
decrement: state => {
return {
...state,
count: state.count - 1,
};
},
},
});
Accepts
slices
An object that defines how the state will be split into contexts.
A slice can be any value (primitive or object)
In this example we will have two state contexts, one for counter
and the other for todos
:
createStore({
slices: {
counter: {
count: 0,
},
todos: {
items: [],
filter: 'all',
},
},
});
reducers
An object with your case reducers. A case reducer is a function that accepts two arguments: state
and payload
and should return the new state
.
For each of the case reducers a corresponding action will be created with the same name.
Returns
Provider
Holds the state, actions and renders all of the contexts. You need to wrap your component tree with it:
return (
<Provider>
{...}
</Provider>
)
useSliceState(sliceKey: string)
Returns the state of the slice whose key you passed in. Only causes a render if you update that slice.
const counterState = useSliceState('counter');
useActions()
Returns all of the actions. Never causes a render because actions
are memoized.
const actions = useActions();
useRedonkState: () => State
Hook for getting the entire state of Redonk. Causes a render on every state update, so use wisely!
const entireState = useRedonkState();
Usage with Typescript
When creating the store, you should define the type of your state:
import { createStore } from 'redonk';
type Todo = {
id: string;
text: string;
isDone: boolean;
};
type AppState = {
counter: {
count: number;
};
todos: {
items: Todo[];
};
};
// like this
createStore({
slices: {
counter: {
count: 0,
},
todos: {
items: [],
},
} as AppState,
});
// or like this
const slices: AppState = {
counter: {
count: 0,
},
todos: {
items: [],
},
};
createStore({
slices,
});
You also need to define the type of the payload for each case reducer:
createStore({
slices: {
counter: {
count: 0,
},
},
reducers: {
// you can name the payload argument anything you want
incrementByAmount: (state, payload: number) => {
return {
...state,
counter: {
...state.counter,
count: state.counter.count + payload,
},
};
},
},
});
And voila! You have intellisense everywhere:
// inside component
const counterState = useSliceState('counter'); // counter state is correctly inferred as { count: number }
const actions = useActions(); // actions are correctly inferred including the type of Payload
const entireState = useRedonkState(); // correctly inferred as AppState