Skip to content

Instantly share code, notes, and snippets.

@typoerr
Last active July 12, 2017 02:10
Show Gist options
  • Select an option

  • Save typoerr/d860c3f899440cb9a4558b00e680f6d5 to your computer and use it in GitHub Desktop.

Select an option

Save typoerr/d860c3f899440cb9a4558b00e680f6d5 to your computer and use it in GitHub Desktop.
import { h, Component, ComponentConstructor, FunctionalComponent } from 'preact';
import { Stream, mergeArray, Subscription } from 'most';
type AnyComponent<P> = ComponentConstructor<P, any> | FunctionalComponent<P>;
export interface Action<T = any, K extends keyof T= any> {
type: K;
payload: T[K];
}
export type Action$<A extends Action = any> = Stream<A>;
export interface MergedCtx<P, S> {
self: Component<P, S>;
}
export interface Epic<P, S, C, K extends keyof S> {
(this: Component<P, S>, action$: Action$, ctx: C): Stream<Pick<S, K>>;
}
export interface Options<P, S> {
updater?: (this: Component<P, S>, nextState: S) => void;
}
export const CONTEXT_KEY = 'STATEFUL_COMPONENT_CONTEXT';
export default function create<P= {}, S = {}>(state: S, options: Options<P, S> = {}) {
return function applyEpics<K extends keyof S, C extends MergedCtx<P, S>>(...epics: Epic<P, S, C, K>[]) {
return function applyComponent(component: AnyComponent<P & S>) {
let subscription: Subscription<any>;
return class WrappedComponent extends Component<P, S> {
static displayName = `Stateful(${component.name})`;
readonly state = state;
protected update = options.updater ? options.updater.bind(this) : this.setState;
constructor(props: any, context: any) {
super(props, context);
const { actionIn$, ...extra } = context[CONTEXT_KEY];
const ctx = Object.assign({}, extra, { self: this });
const epics$ = mergeArray(epics.map(ep => ep.call(this, actionIn$, ctx)));
subscription = epics$.subscribe({
next: (x) => this.update(x),
error: (e) => { throw e; },
complete: () => subscription && subscription.unsubscribe()
});
}
componentWillUnMount() {
subscription && subscription.unsubscribe();
}
render() {
return h(component, { ...this.state as any, ...this.props as any });
}
};
};
};
}
@typoerr
Copy link
Copy Markdown
Author

typoerr commented Jul 11, 2017

import { h, Component } from 'preact';
import * as most from 'most';
import { async } from 'most-subject';
import Dispatcher from '@cotto/dispatcher';
import create, { CTX_KEY, Action$, RequireContext as Ctx } from './../lib/create-observer-component';

interface IActions {
    '$counter:increment': number;
    '$counter:decrement': number;
}


const dispatcher = new Dispatcher<IActions>();
const dispatch = dispatcher.dispatch.bind(dispatcher) as typeof dispatcher.dispatch;


const actionIn$ = async<any>();
dispatcher.subscribe((action) => actionIn$.next(action));


function incrementEpic(action$: Action$, ctx: any) {
    return action$.filter(x => x.type === '$counter:increment')
        .map(x => x.payload)
        .map((x: number) => ({ count: ctx.self.state.count + x }));
}

function decrementEpic(action$: Action$, ctx: any) {
    return action$.filter(x => x.type === '$counter:decrement')
        .map(x => x.payload)
        .map((x: number) => ({ count: ctx.self.state.count - x }));
}

function Counter(props: { count: number, name: string }, ctx: any) {
    return (
        <div>
            <div>{props.name}</div>
            <div>{props.count}</div>
            <div>
                <button onClick={() => ctx.dispatch('$counter:increment', 1)}>+</button>
                <button onClick={() => ctx.dispatch('$counter:decrement', 1)}>-</button>
            </div>
        </div>
    );
}

const CounterContainer = create<{ name: string }, { count: number }>({ count: 0 })
    (incrementEpic, decrementEpic)
    (Counter);


export default class App extends Component<any, {}> {
    getChildContext() {
        return {
            [CTX_KEY]: {
                actionIn$, hello: 'hello'
            },
            dispatch
        };
    }

    render() {
        return <div><CounterContainer name={'my counter'} /></div>;
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment