0.2.1 • Published 3 years ago

flux-condenser v0.2.1

Weekly downloads
6
License
MIT
Repository
github
Last release
3 years ago

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.

0.2.1

3 years ago

0.2.0

3 years ago

0.1.1

3 years ago

0.1.0

3 years ago

0.0.1

4 years ago