Provide LiveQuery (SQL-like) to redux state container
Redux provide a good way to manage the state for React apps, but it lacks query/aggregation operation to compose the single result value you would like to have from multiple redux state (We take redux as database, then redux should provide query-like to our react component). And in reselect, you have to compose your data manually and put yout logic in mutliple nested function.
Redux-livequery can give a live query (SQL-like operation) to group values from multiple redux state together (Indeed, it decouples the direct-subscribe to redux store). It only subscribes the state you care about to give you a better render performance. Whenever the state you care about changes, the result function would be invoked. And then, you can put all your logic to shape the data in one place.
By this above approach, it helps you keep your redux state normalized structures and have extremely simple reducer(no more filter or findIndex operation, we should retrieve the data by indexing not by filtering or finding) as well.
This has peer dependencies of rxjs@5.x.x
, redux
and immutability-helper
, which will have to be installed as well.
npm install --save redux-livequery
(or yarn add redux-livequery)
import { livequeryEnhancer } from 'redux-livequery';
const enhancer = compose(
livequeryEnhancer(),// <===
....
applyMiddleware(....),
window.devToolsExtension ? window.devToolsExtension() : f => f // add support for Redux dev tools,
);
import initialState from '../reducers/initialState';
export const store = createStore(rootReducer, initialState || {}, enhancer);
Now, redux-livequery can push the resultVal back to redux state for sharing the state with different components.
import { livequeryEnhancer, runLivequery } from 'redux-livequery';
import './livequery';
const enhancer = compose(
livequeryEnhancer(),
autoRehydrate(),
applyMiddleware(....),
window.devToolsExtension ? window.devToolsExtension() : f => f // add support for Redux dev tools,
);
import initialState from '../reducers/initialState';
export const store = createStore(rootReducer, initialState || {}, enhancer);
runLivequery(); // after createStore()
// index.js file in ./livequery
import { combineLivequery } from 'redux-livequery';
import someQuery from './someQuery';
const rootLivequery = combineLivequery(
someQuery
);
export default rootLivequery;
// someQuery.js file in ./livequery
import { rxQuerySimple } from 'redux-livequery';
export default function someQuery(store) {
console.log("someQuery()");
let selector0 = (state) => state.task.isComplete;
let selector1 = (state) => state.task.isActive;
rxQuerySimple([selector0, selector1], ['isComplete', 'isActive'], (completeActive) => {
// you can do whatever you want here
// ex: filter, reduce, map
let isCompleteNotActive = {};
for (let key in completeActive.isComplete) {
if (!(key in completeActive.isActive)) {
isCompleteNotActive[key] = completeActive.isComplete[key];
}
}
// set data into redux state
store.dispatch({ type: "SET_COMPLETE_NOT_ACTIVE", payload: { isCompleteNotActive } });
}, 0);
}
import { rxQueryLeftJoin } from 'redux-livequery';
//import { rxQueryBasedOnObjectKeys } from 'redux-livequery'; // This will be removed at next version
// Note: Rename rxQueryBasedOnObjectKeys into rxQueryLeftJoin.
// rxQueryBasedOnObjectKeys still work, the both operation is identical.
- selectors (Array): Choose the state you want to observe, the child's key of Objec selected by the first selector is primary key set.
- fields (Array): Give each selector a field name
- resultFunc (Function): The callback to be invoked whenever any state you select changes, the result value would be composed and have the key and field that owns immutable Object.
- debounceTime (Number, Default: 0): Time(ms) to debounce the trigger of resultFunc
(Function): A function that unsubscribes the live query.
import { rxQueryInnerJoin } from 'redux-livequery'; New API: 2017-5-6
This API will reactively get the intersection of the key set by scaning Object selected by each selector.
The resultFunc would be invoked only on the condition intersection set is not empty (or the size of intersection is not zero) and the state you would like to observe changes.
- selectors (Array): Choose the state you want to observe, the selector is to select the Object that has the child key.
- fields (Array): Give each selector a field name
- resultFunc (Function): The callback to be invoked whenever any state you select changes, the result value would be composed and have the key and field that owns immutable Object.
- debounceTime (Number, Default: 0): Time(ms) to debounce the trigger of resultFunc
(Function): A function that unsubscribes the live query.
import { rxQueryFullOuterJoin } from 'redux-livequery'; New API: 2017-5-9
// Note: rename rxQueryOuterJoin into rxQueryFullOuterJoin
This API will reactively get the union of the key set by scaning Object selected by each selector.
The resultFunc would be invoked only on the condition union set is not empty (or the size of union is not zero) and the state you would like to observe changes.
- selectors (Array): Choose the state you want to observe, the selector is to select the Object that has the child key.
- fields (Array): Give each selector a field name
- resultFunc (Function): The callback to be invoked whenever any state you select changes, the result value would be composed and have the key and field that owns immutable Object.
- debounceTime (Number, Default: 0): Time(ms) to debounce the trigger of resultFunc
(Function): A function that unsubscribes the live query.
import { rxQueryLeftOuterJoin } from 'redux-livequery'; New API: 2017-5-19
The result of a left outer join for tables A and B always contains all rows of the "left" table (A), even if the join-condition does not find any matching row in the "right" table (B).
The resultFunc would be invoked only on the condition result set is not empty and the state you would like to observe changes.
- selectors (Array): Choose the state you want to observe, the selector is to select the Object that has the child key.
- fields (Array): Give each selector a field name
- resultFunc (Function): The callback to be invoked whenever any state you select changes, the result value would be composed and have the key and field that owns immutable Object.
- debounceTime (Number, Default: 0): Time(ms) to debounce the trigger of resultFunc
(Function): A function that unsubscribes the live query.
import { rxQuerySimple } from 'redux-livequery'; New API: 2017-5-6
This API will give you simple select operation.
- selectors (Array): Choose the state you want to observe, the selector is to select the Object or Array.
- fields (Array): Give each selector a field name
- resultFunc (Function): The callback to be invoked whenever any state you select changes.
- debounceTime (Number, Default: 0): Time(ms) to debounce the trigger of resultFunc
(Function): A function that unsubscribes the live query.
import { rxQueryLeftJoin } from 'redux-livequery';
...
constructor(){
...
let selector0 = (state) => state.favorite;// The child's key of Object selected by first selector would be major key set.
let selector1 = (state) => state.profile;
//state.favorite={storeId1: Object1, storeId2: Object2},
//state.profile={storeId1: Object3, storeId2: Object4, storeId3:Object5}
let field0 = 'favor';
let field1 = 'profile';
this.unsubscribe = rxQueryLeftJoin([selector0, selector1], [field0, field1], (result) => {
// equals SQL query:
// SELECT * FROM profile LEFT JOIN favorite ON profile.id=favorite.id;
let favoriteList = result;
console.log(`next:`, favoriteList);
// result value would be [{key:storeId1, favor:{Object1}, profile:{Object3}}
// {key:storeId2, favor:{Object2}, profile:{Object4}}]
// Below here you can do whatever you want here, for example
// map(), filter(), reduce(), flatten()
// this.setState({favorList:favoriteList}); //set local state
// or
// dispatch({type:'ACTION_NAME', payload:favoriteList}); // set redux state
// whenever state.favorite or state.profile(API will dynamically subscribe) change, the result function would be invoked
});
componentWillUnmount(){
// after a while, unsubscribe the livequery
// exec unsubscribe when you don't need to observe the value
this.unsubscribe();
}
}
import { rxQueryInnerJoin } from 'redux-livequery';
...
constructor(){
...
let selector0 = (state) => state.favorite;
let selector1 = (state) => state.profile;
//state.favorite={storeId1: Object1, storeId2: Object2},
//state.profile={storeId2: Object4, storeId3:Object5}
let field0 = 'favor';
let field1 = 'profile';
this.unsubscribe = rxQueryInnerJoin([selector0, selector1], [field0, field1], (result) => {
// equals SQL query:
// SELECT * FROM profile INNER JOIN favorite ON profile.id=favorite.id;
console.log(`result:`, result);
// result value would be [{key:storeId2, favor:{Object2}, profile:{Object4}}]
// Below here you can do whatever you want here, for example
// map(), filter(), reduce(), flatten()
// this.setState({...}); //set local state
// or
// dispatch({...}); // set redux state
// whenever state.favorite or state.profile(API will dynamically subscribe) change, the result function would be invoked
});
componentWillUnmount(){
// after a while, unsubscribe the livequery
// exec unsubscribe when you don't need to observe the value
this.unsubscribe();
}
}