redux-decor
v1.0.5
Published
Lightweight decorations for redux
Downloads
11
Readme
Redux Decorators
Requirements
Of course you can use only functions from this library, but I recommend to enable decorators support. See https://www.npmjs.com/package/babel-plugin-transform-decorators-legacy
Usage
// file 'MyModel.js'
import { Storage, Transient, AsyncAction, Action, selector } from 'redux-decor'
// Make reducer, inject it into redux store.
// Also mix into the class static actions and static 'getState()' - returns current redux state for that reducer.
// Add to the instance object methods 'getState()', 'dispatch()' - from redux store object.
// By default it uses class name as reducer name, but you can give alias like this '@Storage("TheirsModel")'
@Storage
export default class MyModel {
// Provide properties for state and create initial state.
forPerson = undefined;
secretSauce = null;
fromPerson = null;
toPerson = null;
error = null;
loading = false;
// Not sure if it really needed, but you can mark property as transient - it shall not pass to redux store
@Transient
transientField = 'should be ignored';
// Mark class method as async action.
// Any changes to 'this' will be ignored, only 'Promise.resolve' result will be used to update redux state.
@AsyncAction({ // override default callbacks
request: { loading: true }, // as plain object, just set 'this.loading = true'
ok: function() { this.loading = false }, // as function, 'this' will be bound to redux state
error: selector('fetchSecretSauceError') // use 'selector' helper, target method of class ( see below )
})
makeASandwichWithSecretSauce(forPerson) {
var self = this;
return this.fetchSecretSauce().then(
sauce => self.makeASandwich(forPerson, sauce),
error => {
self.apologize('The Sandwich Shop', forPerson, error);
throw error; //continue promise chain
}
);
}
// Used as error callback from 'makeASandwichWithSecretSauce' action
fetchSecretSauceError(error) {
this.loading = false;
this.error = error;
}
// Mark class method as plain action.
// You don't need to return anything, but changes to 'this' will be used to update redux state
@Action
makeASandwich(forPerson, secretSauce) {
this.forPerson = forPerson;
this.secretSauce = secretSauce;
return {
forPerson,
secretSauce
};
}
@Action
apologize(fromPerson, toPerson, error) {
this.fromPerson = fromPerson;
this.toPerson = toPerson;
this.error = error;
}
// Just method
fetchSecretSauce() {
return fetch('https://www.google/search?q=secret+sauce').then(r => r.status);
}
}
const logger = createLogger({
collapsed: true,
duration: true,
diff: true
});
setDefaultOptions({
asyncAction: {
request: function () {
this.error = null;
this.isLoading = true;
},
ok: function (payload) {
Object.assign(this, payload);
this.isLoading = false;
},
error: function (error) {
this.error = error;
this.isLoading = false;
}
}
});
// file init-store.js
import { applyMiddleware, combineReducers } from 'redux';
import { createLogger } from 'redux-logger';
import { createStore, setDefaultOptions } from 'redux-decor';
import MyModel from './MyModel';
// override default callbacks
setDefaultOptions({
asyncAction: {
request: function () {
this.error = null;
this.isLoading = true;
},
ok: function (payload) {
Object.assign(this, payload);
this.isLoading = false;
},
error: function (error) {
this.error = error;
this.isLoading = false;
}
}
});
const logger = createLogger({
collapsed: true,
duration: true,
diff: true
});
const navReducer = (state = initialState, action) => {
const nextState = ...
// Simply return the original `state` if `nextState` is null or undefined.
return nextState || state;
};
const store = createStore(
combineReducers({Navigation: navReducer}), // provide custom reducer
{Navigation: initialState}, // and custom initial state
applyMiddleware(logger) // middlewares
);
console.log("call action makeASandwich = ", MyModel.makeASandwich('for me', 'no sauce'));
console.log("actual state for MyModel = ", MyModel.getState());
console.log("also call action as instance method = ", new MyModel().makeASandwichWithSecretSauce('for you'));