ez-react
Version:
A small react-connector for ez-flux
72 lines (58 loc) • 2.39 kB
JSX
/* @flow */
/* eslint-disable no-return-assign, react/sort-comp */
import * as React from 'react';
type Store = Object;
type Options = { shouldListen: boolean };
type Stores = { [storeName: string]: Store };
type StoreHandler = (Store, props: Object) => Object | void;
type StoreHandlers = { store: Store, handler: StoreHandler }[];
type Handler = StoreHandler | string[];
type Handlers = { [storeName: string]: Handler };
type Events = { store: Store, listener: () => void }[];
const handlerErr = '2nd arg must be an object mapping state keys to eventHandlers';
export function normaliseHandler(handler: Handler): StoreHandler {
if (typeof handler === 'function') return handler;
if (Array.isArray(handler)) {
return store => handler.reduce((acc, k) => { acc[k] = store[k]; return acc; }, {});
}
throw new Error(handlerErr);
}
export default function createConnector(
stores: Stores,
opts: Options = { shouldListen: true },
): Function {
return function connect(Component: Function, handlers: Handlers): Function {
if (typeof Component !== 'function') throw new Error('1st arg must be a React Component');
if (!handlers || typeof handlers !== 'object') throw new Error(handlerErr);
const storeHandlers: StoreHandlers = Object
.keys(handlers)
.filter((k) => { if (!stores[k]) throw new Error(`store "${k}" not found`); return true; })
.map(k => ({ handler: normaliseHandler(handlers[k]), store: stores[k] }));
return class EZWrapper extends React.Component<Object, Object> {
// Set a readable displayName
static displayName = `EZWrapper(${Component.name})`;
events: Events;
constructor(props) {
super(props);
this.state = {};
}
componentWillMount() {
this.events = storeHandlers.map(({ store, handler }) => {
const listener = () => {
const newState: any = handler(store, { ...this.state, ...this.props });
if (newState) this.setState(newState);
};
if (opts.shouldListen) store.$on('change', listener);
listener();
return { store, listener };
});
}
componentWillUnmount() {
if (opts.shouldListen) this.events.forEach(e => e.store.$off('change', e.listener));
}
render() {
return <Component {...this.state} {...this.props} />;
}
};
};
}