flux-condenser
v0.2.1
Published
**Condenser**: _Another term for capacitor._
Downloads
3
Readme
Flux Condenser
Condenser: Another term for capacitor.
This is a small lightweight but powerful, implementation of Facebook's Flux architecture.
Important Flux Condenser is not compatible with IE.
Why to make another Flux implementation?
This implementation differs, from other implementations of Flux, in a fundamental aspect: Subscriptions.
Stores are subscribed to the dispatcher to listen to actions separately.
This means that only those callbacks subscribed to the dispatched action are executed, therefore the developer doesn't need to implement the huge switch
to determine which action was called.
Also, whatever needs to subscribe to changes in a Store, it does through a subscription to an extractor function. Every time the store changes, it runs every extractor annotated to it, and for each extractor that changed from the previous execution, subscribers are notified of the change.
Parts
Dispatcher
The dispatcher is the main orchestrator of the architecture. Every action is sent through the dispatcher and the dispatcher will inform each listener registered to that action.
It is highly recommended to use a single dispatcher for the entire application, although nothing prevents the developer to create multiple dispatchers instances if it is required.
Global dispatcher
The global dispatcher is accessible from:
import { dispatcher } from 'flux-condenser';
or
const fluxCondenser = require('flux-condenser');
fluxCondenser.dispatcher;
Stores
Stores are where the data lives. They must subscribe to a dispatcher to process the actions sent to them.
createStore
There is a createStore
helper function to easily create a store that is connected to the global dispatcher.
Usage
import { createStore } from 'flux-condenser';
// or
const { createStore } = require('flux-condenser');
const storeName = Symbol('store6');
const initialValue = { count: 0};
const condenser = function (state) {
return {
count: state.count + 1,
};
};
const condensers = [
['ACCUMULATE', condenser],
];
const store = createStore(storeName, initialValue, condensers);
export store;
// or
module.exports.store = store;
Extractors
Extractors are functions that receive the state
as a parameter and return a part of that state
. For example:
function extractorExample(state) {
return state.interestingProperty;
}
Adding an extractor to a store
A common use for the store is to listen to changes in the store's state. To do that, stores have a method subscribe
that accepts a data handler and an extractor as arguments, for example:
store.subscribe(
function dataHandler(data) {
// Do something with your data
},
function extractor(state) {
// Return part of your
},
);
The dataHandler
function will receive as an argument, whatever the extractor
function returns.
Several dataHandler
functions can be attached to a single extractor
, so it is a good idea to share the extractor function to be reused wherever is needed, for example:
extractor.js
export function getMessageCounter(state) {
state.counter;
}
header.js
import { getMessageCounter } from '/extractor.js';
import { store } from 'stores.js';
store.subscribe(function (counter) {
document.title = '(' + counter + ') messages';
}, getMessageCounter);
messages.js
import { getMessageCounter } from '/extractor.js';
import { store } from 'stores.js';
store.subscribe(function (counter) {
document.getElementById('messages-badge').textContent = counter;
}, getMessageCounter);
createExtractor
createExtractor
is a helper function to create extractors that require extra parameters besides the state
. There is also createMemoExtractor
helper that provides a level of cache (memoization) returning the same function for the same input parameters.
Usage
import { createExtractor } from 'flux-condenser';
// or
const { createExtractor } = require('flux-condenser');
const getOptionForIdExtractor = createExtractor(function (state, id) {
return state.options[id];
});
// store was created before
store.subscribe(function (option) {
// Do something with the options.
}, getOptionForIdExtractor('id1'));
store.subscribe(function (option) {
// Do something with the options.
}, getOptionForIdExtractor('id2'));
Execute extractors on demand
Stores can also execute extractors on demand. When that happens, the store will execute the given extractor against its state
and return the extractor result.
Usage
import { createExtractor } from 'flux-condenser';
// or
const { createExtractor } = require('flux-condenser');
const getOptionForIdExtractor = createExtractor(function (state, id) {
return state.options[id];
});
// store was created before
const option = store.execExtractor(getOptionForIdExtractor('id1'));
Actions
Actions are more like a concept rather than a function perse. Raising an action is just calling the dispatcher with an action name and a payload, to be spread to those stores that are subscribed to that action.
There are two ways to dispatch an action:
From a dispatcher
dispatcher.dispatch({ action: 'ACTION_NAME', payload: { property: value } });
From a store
// store was created before
store.dispatch({ action: 'ACTION_NAME', payload: { property: value } });
createActionDispatcher
createActionDispatcher
is a helper function to create action dispatchers, which will dispatch actions on the global dispatcher.
Usage
import { createActionDispatcher } from 'flux-condenser';
// or
const { createActionDispatcher } = require('flux-condenser');
const action1Dispatcher = createActionDispatcher('ACTION_1', (property1, property2) => {
return {
property1,
property2,
};
});
// This will call the global dispatcher with an action object like {action: 'ACTION_1', payload: {property1: 'value1', property2: 'value2'}}
action1Dispatcher('value1', 'value2');
// This will call the global dispatcher with an action object like {action: 'ACTION_1', payload: {property1: 'value3', property2: 'value4'}}
action1Dispatcher('value3', 'value4');
Typescript
This project implements TypeScript definitions for the use in TypeScript projects.
Usage
/// <reference types="node" />
import { createStore, createExtractor } from 'flux-condenser';
const storeName = Symbol('my-store');
// actions
export enum Actions {
SET_HEADER = 'SET_HEADER',
SET_SUB_TITLE = 'SET_SUB_TITLE',
}
// Store data types
type Header = {
title: string;
subTitle: string;
};
type Body = {
amount: number;
children: Record<string, string>;
};
type Data = {
header?: Header;
body?: Body;
};
// condensers
function setHeader(initialState: Data, payload: Header): Data {
return {
...initialState,
header: payload,
};
}
function setBody(initialState: Data, payload: Body): Data {
return {
...initialState,
body: payload,
};
}
// Create a store
export const store = createStore<Data, Actions>(storeName, {}, [
[Actions.SET_HEADER, setHeader],
[Actions.SET_SUB_TITLE, setBody],
]);
// Extractors
const getBodyExtractor = (data: Data) => {
return data.body;
};
// Extractors creators
const extractorFunction = (data: Data, id: string): string | undefined => {
return data.body && data.body.children[id];
};
const extractor = createExtractor<Data, [string], string | undefined>(extractorFunction);
// Add extractors to store
store.subscribe((body: Body | undefined) => {
// do something with the body
}, getBodyExtractor);
store.subscribe((value: string | undefined) => {
// do something with the value
}, extractor('some id'));
Webpack with multiple bundles
Flux Condenser module should be included only once per application. In a multiple bundle Webpack solution, it means we need to extract the Flux Condenser module in a separate bundle that is going to be used by every other bundle. Webpack must be configured with the optimization runtimeChunk option to create a runtime chunk with common modules.
module.exports = {
entry: {...},
output: {...},
module: {...},
optimization: {
runtimeChunk: 'single',
...
},
};
Collaboration
I am working on the first fully implemented version of this library, once that is done, I will accept PRs.