Repatch is just a simplified Redux, that let you create actions more briefly by dispatching reducers directly.
store.dispatch(state=>({ ...state,counter: state.counter+1}));In this terminology, an action is a function that returns a reducer:
constincrement=amount=>state=>({ ...state,counter: state.counter+amount});store.dispatch(increment(42));Redux has verbose action management. The most of redux projects do not need sctrict action administration. Action types, action creators and the reducer's action handlers are mutually assigned to each other. Repatch's purpose is creating actions briefly.
The simplest way to keep the immutable action controlled dataflow and define actions briefly is dispatching pure functions (as reducers) to the store.
Comparison with Redux
Repatch is
- less verbose
- smaller (the minified version is less than 1 KB)
- faster
than Redux.
Working with Redux
If you have to keep the official Redux in your project, then you can use the redux-repatch or redux-repatch-creator enhancers.
Repatch - the simplified Redux
npm install --save repatch importStorefrom'repatch';conststore=newStore(initialState);constStore=require('repatch').Store;<scriptsrc="https://unpkg.com/repatch/dist/repatch.js"></script>or the minified bundle:
<scriptsrc="https://unpkg.com/repatch/dist/repatch.min.js"></script>and
constStore=Repatch.Store;constthunk=Repatch.thunk;Compatibility with react-redux
Repatch's interface is very similar to Redux, therefore you can use with react-redux.
constunsubscribe=store.subscribe(()=>console.log(store.getState()));store.dispatch(resolveFetchingUsers(users));unsubscribe();conststore=newStore([]);constaddTodo=text=>todos=>[...todos,{ text,checked: false}];constcheckTodo=index=>todos=>todos.map((todo,i)=>(i===index ? { ...todo,checked: !todo.checked} : todo));consteditTodo=(index,text)=>todos=>todos.map((todo,i)=>(i===index ? { ...todo, text } : todo));constremoveTodo=index=>todos=>todos.filter((_,i)=>i!==index);We do not need to reduce always the whole state of the store. Repatch also offers a way to combine sub-reducers, those describe a deeply nested property in the state. We just define a helper function that takes a nested reducer as argument, and returns a reducer that reduces the whole state:
constreduceFoo=fooReducer=>state=>({ ...state,bar: { ...state.bar,foo: fooReducer(state.bar.foo)}});Using that we can define easily an action, that sets an x property in the foo object:
constsetX=x=>reduceFoo(state=>({ ...state, x }));A repatch middleware takes the store instance, a next function and the previous reducer. The middleware can provide a new reducer via the next function.
Middleware: Store->Next->Reducer->anyUse the addMiddleware method to chaining middlewares:
conststore=newStore(initialState).addMiddleware(mw1).addMiddleware(mw2,mw3);This simple logger middleware logs the current- and the next state:
constlogger=store=>next=>reducer=>{conststate=store.getState()constnextState=reducer(state)console.log(state,nextState)returnnext(_=>nextState)}conststore=newStore(initialState).addMiddleware(logger)The thunk middleware is useful for handling async actions similar to redux-thunk.
importStore,{thunk}from'repatch';conststore=newStore(initialState).addMiddleware(thunk);In thunk async actions reducer returns a function (delegate):
constupdateUser=delta=>state=>async(dispatch,getState)=>{try{consteditedUserId=getState().editedUser;dispatch(toggleSpinner(true));awaitapi.updateUser(editedUserId,delta);awaitdispatch(fetchUsers());}catch(error){dispatch(state=>({ ...state,error: error.message}))}finally{dispatch(toggleSpinner(false));}};It is possible to embed async actions within each other too and awaiting their resolving:
awaitdispatch(fetchUsers());It is possible to inject extra arguments into async actions:
importStore,{thunk}from'repatch';importapifrom'./api';import{hashHistory}from'react-router';conststore=newStore(initialState).addMiddleware(thunk.withExtraArgument({ api, hashHistory }));Then you can access these arguments in your delegates:
constupdateUser=delta=>state=>async(dispatch,getState,{ api, hashHistory })=>{// ...}This way you can keep your async actions independently from outer instances or side-effects. This practice is useful for testing.
Testing a reducer is easy:
import*asassertfrom'assert';import{changeName}from'./actions';// ...it('changeName',()=>{conststate={name: 'john'};constnextState=changeName('jack')(state);assert.strictEqual(nextState.name,'jack');});For async action tests you need to instantiate the Store and provide mocked extra arguments.
importStore,{thunk}from'repatch';import*asassertfrom'assert';import{fetchUsers}from'./actions';constmockUsers=[{username: 'john'}];constmockApi={getUsers: ()=>Promise.resolve(mockUsers)}// ...it('fetchUsers',async()=>{conststate={users: []};conststore=newStore(state).addMiddleware(thunk.withExtraArgument({api: mockApi}));awaitstore.dispatch(fetchUsers());constnextState=store.getState();assert.deepEqual(nextState.users,mockUsers);});

