Trivial Redux is the library for fast creating API layer that gives you trivial CRUD from the box. It generates actions and reducers for standard REST queries. The reducers are easy expandable through creating your own reducer that has convenient interface for access to the generated reducer, action types and the default state.
Trivial Redux was creating for generating only the part of the store structure(api entities, generally). There are some tasks that don't fit the pattern and it is easier to solve them without this library.
- Installation
- Getting started
- Recommended file structure
- Reducers override
- Decorators
- The endpoint state structure
- Actions description
- Configuration object
- Custom async types generator
- Custom types
- Immutability
- Roadmap
Install packages via npm
npm install trivial-redux trivial-redux-middleware --save
or yarn
yarn add trivial-redux trivial-redux-middleware
During store initialization pass the middleware and generated reducers
import {createStore, combineReducers, applyMiddleware} from 'redux'
import {combineEndpoints} from 'trivial-redux'
import trivialReduxMiddleware from 'trivial-redux-middleware'
import endpoints from './endpoints'
import reducers from './modules'
const api = combineEndpoints(endpoints)
const rootReducer = combineReducers({...reducers, ...api.reducers })
const middlewares = [
trivialReduxMiddleware
]
export default createStore(rootReducer, applyMiddleware(...middlewares))
trivialRedux is the fabric for creating api object:
import {combineEnpoints, rest} from 'trivial-redux'
const api = trivialRedux(
{
todos: rest({entry: 'http://some_site.com/todos'}),
comments: rest({entry: 'http://some_site.com/posts'})
}
)
We got the api object that contains:
- reducers - the object with reducers for todos and comments
- actions - the object contains the objects for todos and comments contains action creators for standard rest queries + reset action for clearing the collection in the store
- types - action types for our entities
So, then we can do something like this:
store.dispatch(api.actions.todos.index())
The endpoint configuration may be more detailed, then we use object instead of url string. For example, Trivial Redux contains some types of endpoints - rest, fetch and setter. All endpoints are considered as rest by default, but it can be changed:
combineEndpoints(
todos: fetch({
entry: '...'
})
)
All options for configuration object you may see below.
We recommend to keep complex endpoint in separate files:
|-- api.js
|-- endpoints
|-- todos.js
|-- comments.js
And aggregate them in main entry point
// api.js
import {combineEndpoints} from 'trivial-redux'
import todos from './endpoints/todos'
import comments from './endpoints/comments'
export default combineEndpoints(
{
todos,
comments
}
)
You can define your own reducer in the configuration object. It will have access to the standard trivial-redux reducer through this.reducer and types through this.types.
Note: this in reducer is immutable context for more convenient pass of useful data from trivial-redux to your reducer. You can't use it to save any your state.
combineEndpoints(
todos: rest({
entry: '...',
reducer: function(state, action){
switch(action.type){
case this.types.index.success:
// We can do some custom logic here
// and generate state by our result and the stanard reducer result
return {...someResult, this.reducer(state, action) }
case this.types.destroy.failure:
// or we can not use the standard reducer
// do something
// return something
case SOME_ACTION_TYPE:
// or catch any other action types
return null
default:
return this.reducer(state, action)
}
}
})
)
{
lastUpdatedAt: null,
data: {
collection: null,
current: null,
oldCurrent: null
},
fetching: false
}
- lastUpdatedAt - the timestamp for last update
- collection - the array of entities
- current - the property for keeping show action result
- oldCurrent - the property for keeping old current version for optimistic updates(not using now)
- fetching - the flag of fetching state
{
lastUpdatedAt: null,
data: null,
fetching: false
}
null
- params - url params for query
- id - entity id
- data - data for creating new entity
- id - entity id for update
- data - changed entity data
- id - entity id for destroy
Loads next page of entity collection and append it to state(for huge collections, instead of index). Redux-thunk required.
- params - url params for filtering
Clears data.collection
You may pass the global configuration object as second argument of trivialRedux fabric. The endpoints's settings are override global.
Entry url for the endpoint.
The type of endpoint, rest or fetch.
The option for skip .json postfix that concatenates by default.
The custom reducer for your own logic. If it is null, the reducer for the current endpoint will be omitted.
The initial state for the reducer
The array of reducer decorators. Decorator is a function which takes reducer and wraps it with its own custom logic.
The host for url prefix
There are some helpers in trivial redux for getting types:
api.typesFor
- reducer's
this.typesFor
- reducer's
this.types
- reducer's
this.allTypes
These helpers are bound to trivial-redux instance settings. There is also the helper actionTypesFor
from trivial-redux package. This helper is not bound to trivial-redux instance settings and may be incompatible with with the above helpers.
Besides the built-in types you may use your own types. Define you type like below:
const type = <M, S extends DefaultInitialState<M> = DefaultInitialState<M>>(
options: TrivialReduxEndpointOptions<S, TypeActions<S>, TypeAsyncActions> = {}
) : InternalTrivialReduxType<S, TypeActions<S>, TypeAsyncActions, TypeAsyncActionsTypes<M>> => {
name: 'custom-setter',
options,
// all reducers of types works on immer
// It means you should mutate state here or return new state(not both)
reducer(entityName, initialState) {
return function(state = initialState, action) {
switch(action.type) {
case this.types.set
return action.payload
case this.types.reset
return cloneDeep(initialState)
else
return state
}
}
},
actions(entityName, endpoint, settings) {
return {
set(data) {
return {payload: data}
}
reset(){
return {}
}
}
}
asyncActions(entityName, endpoint, settings) {
return {
load() {
return {
meta: {
fetch: {
url: '...'
}
}
}
}
}
}
export default createType(type, null)
Action types will be generated based on action name and type(sync/async). You may also specify your type explicit.
After you may use your type in schema as following:
export default combineEndpoints(
{
todo: customSetter()
}
)
Trivial redux uses Immer to provide immutable reducers. You may pass immer: true
in global settings(or for current endpoint), and your reducers will work in immutable way, no other changes is needed.
- Custom actions
- Aliases
- Extended decorators