README
Rewrapped
Typescript / Javascript Redux dispatcher, action and reducer wrapper with a special emphasis on:
- Minimal boilerplate for maximum readability and ergonomics
- Complete typesafety for Typescript and strong autocompletion / refactoring support for Javascript
- Immutable updates through a mutable API powered by Immer
- Support for easy interop with and incremental migration from traditional Redux reducers.
NOTE: Below code samples are written in Typescript.
Javascript users can remove type annotations while still levaraging the IDEs Typescript interpreter (VSCode provides best support)
Basic Usage
import { manageModule } from 'rewrapped';
export const todoModule = manageModule({
todos: new Array<{ todo: string, done: boolean }>(),
editingIndex: -1,
})({
Add: (state, todo: string) => state.todos.push({ todo, done: false }),
Update: (state, todo: string) => state.todos[state.editingIndex].todo = todo,
Remove: (state, index: number) => state.todos.splice(index, 1),
ToggleDone: (state, index: number) => state.todos[index].done = !state.todos[index].done,
EditIndex: (state, index: number) => state.editingIndex = index,
});
todoModule.Add('Relax'); // dispatch
Full Demo: Typescript | Javascript
Setup
npm i redux react-redux rewrapped
npm i --dev @types/redux @types/react-redux
import { createStore, combineReducers } from 'redux';
import { configureRewrapped } from 'rewrapped';
const store = createStore(combineReducers({
todo: todoModule.reducer,
//... other module's reducers...
}));
configureRewrapped({
store,
});
Async-updates
To update state after a promise settles, new actions can be dispatched.
import { manageModule } from 'rewrapped';
const todoModule = manageModule({
todos: new Array<string>(),
})({
AddTodo: (state, todo: string) => state.todos.push(todo),
SaveNewTodo: (state, todo: string) => {
return () => remotelySaveTodo(todo)
// .then(res => state.todos.push(res.savedTodo)) // Won't work: directly updating the state variable
.then(res => todoModule.AddTodo(res.savedTodo)) // Will work: dispatching a new action
},
});
Direct Updates
Some state updates are straightforward and repetitive (such as storing various collections retrieved from an API).
In such cases, you can bypass writing a dedicated module function by using the built-in directUpdate()
function as follows.
todoModule.directUpdate(s => s.todos).assign(todosReceivedFromApi)
NOTE: Using directUpdate()
will compromise testability and discoverability because it will de-centralize your state logic.
Use this function sparingly for simple updates. Prefer creating dedicated module functions.
Dispatcher-tags
Your debugging experience can be improved by enforcing that a tag (identifying the source of the dispatch) be supplied when dispatching actions.
While this has no effect on functionality, the library will prefix the action type
with this tag providing greater transparency in the Redux devtools.
To enforce dispatcher tags, use manageModuleTagged
instead of manageModule
when creating a module.
(Note that, for webpack users, it may be convenient to use the __filename as the dispatcher tag)
import { manageModuleTagged } from 'rewrapped';
const myModule = manageModuleTagged({
prop: '',
})({
UpdateProp: (state, prop: string) => state.prop = prop,
});
myModule.UpdateProp('MyComponent', 'Hello'); // Here the dispatcher tag is 'MyComponent'. The payload is 'Hello'.
Waiting for dispatch to complete
All dispatches will return a promise
todoModule.AddTodo('Work out').then(r => console.log(`Returned ${r.returned}, Produced: ${r.produced}`))
Deriving payload type from initial state
It can be useful to extract the initialState
into its own variable so that that object can be referenced when declaring the payload type.
const initialState = {
some: {
thing: {
propOne: '',
propTwo: false,
}
}
};
export const myModule = manageModule(initialState)({
UpdateThing: (state, thing: typeof initialState.some.thing) => state.some.thing = thing,
});
myModule.UpdateThing({ propOne: 'hello', propTwo: true, });