Skip to content

Latest commit

 

History

History
executable file
·
127 lines (106 loc) · 4.4 KB

subscribe.md

File metadata and controls

executable file
·
127 lines (106 loc) · 4.4 KB

Subscribe

Store interface in Redux provides you subscribe() method. But there is only global subscribe(). What if you want to subscribe for low-level store changes? Let's say, we have 3 low-level states:

  • boolean toggle state1
  • array of 2 number states (state2 and state3)

And our task is to aggregate them in high-level state: {toggle: state1, inc: [state2, state3]}.

Define first our low-level reducers and the high-level state structure:

const toggle = (state, action) => state === undefined ? false : (action.type === 'TOGGLE' ? !state : state);
const inc = (state, action) => state === undefined ? 0 : (action.type === 'INCREMENT' ? state + 1 : state);
let highLevelState = {toggle: false, inc: [1, 2]};

According to design our composite structure would be:

const composite1 = Structure({
    toggle,
    inc: [inc, inc]
});

Having the array of listeners, now we can define our high-level dispatch() method returned by original createStore() (like Redux):

let listeners = [];

const highLevelDispatch1 = (reducer => action => {
    const newState = reducer(highLevelState, action);
    if (newState !== highLevelState) {
        highLevelState = newState;
        listeners.map(listener => listener());
    }
})(composite1.reducer);

const createStore1 = () => ({
    getState: () => highLevelState,
    dispatch: highLevelDispatch1,
    subscribe: listener => {
        listeners.push(listener);
        return () => listeners = listeners.filter(l => l !== listener);
    }
});

Ok, preconditions are set. And then after initializing the composite, we receive needed store subscribe() method for each low-level state:

composite1.createStore({createStore: createStore1})();
composite1.store.toggle.subscribe(({getState}) => {
    if (getState()) {
        composite1.store.inc[1].dispatch({type: 'INCREMENT'});
    }
});

You can also subscribe from the top level to any low-level state change:

composite1.subscribe({
    toggle: ({getState, dispatch}) => {
        if (getState()) {
            dispatch({type: 'TOGGLE'});
        }
    }
});

So, what will happen if we dispatch TOGGLE action type:

composite1.store.toggle.dispatch({type: 'TOGGLE'});
// highLevelState is {toggle: false, inc: [1, 3]}

When we've dispatched TOGGLE, state1 become true - and first subscriber triggered - and dispatched INCREMENT on state3 of the inc array. That's how state3 transformed from 2 to 3. And then second subscriber triggered, because state1 is true - it dispatched the TOGGLE again. That's how state1 become false again.

What if our sub-states are already complex and have internal structure inside? For example:

highLevelState = {toggle: false, inc: [1, 2]};
const composite2 = Structure({
    toggle,
    inc: Structure([inc, inc])
});

listeners = [];
const highLevelDispatch2 = (reducer => action => {
    const newState = reducer(highLevelState, action);
    if (newState !== highLevelState) {
        highLevelState = newState;
        listeners.map(listener => listener());
    }
})(composite2.reducer);

const createStore2 = () => ({
    getState: () => highLevelState,
    dispatch: highLevelDispatch2,
    subscribe: listener => {
        listeners.push(listener);
        return () => listeners = listeners.filter(l => l !== listener);
    }
});

Notice, that inc is not simply an array anymore, but structure of the array. Than the inc property of store would be devided into store to access store methods (like subscribe()) and structure to access internal store of sub-structure:

composite2.createStore({createStore: createStore2})();

const unsubscribe = composite2.store.inc.store.subscribe(() => composite2.store.toggle.dispatch({type: 'TOGGLE'}));
composite2.store.inc.structure[1].dispatch({type: 'INCREMENT'});
// highLevelState is {toggle: true, inc: [1, 3]}
unsubscribe();

When we've dispatched INCREMENT for the state3, inc state changed - that's why the listener was triggered. The listener dispatched TOGGLE - and the state1 became true.

And, as you can see, each subscribe() method returns unsubscribe() (if your original subscribe() method returns unsubscribe() in createStore() as in Redux)

This way we have subscribe() methods for each sub-state... and even sub-sub-states and so on.

Full example could be found in examples/subscribe.js

Read next: Memoize