diff --git a/demo/demos/WithHandlersDemo.tsx b/demo/demos/WithHandlersDemo.tsx new file mode 100644 index 0000000..24f972b --- /dev/null +++ b/demo/demos/WithHandlersDemo.tsx @@ -0,0 +1,32 @@ +import * as React from 'react'; +import { withState } from '../../src/lib/withState'; +import { ChainableComponent } from '../../src/ChainableComponent'; +import Step from '../Step'; +import { DoBuilder } from '../../src'; +import { withHandler } from '../../src/lib/withHandler'; + +export const WithHandlersDemo = + DoBuilder + .bind('b', withState(0)) + .bind('a', withState(0)) + .bindL('handler', ({a, b}) => { + console.log('handler', a.value, b.value) + return withHandler(() => alert(`A's count is : ${a.value}`), [b.value]) + }) + .done() + .render(({a, b, handler}) => ( +
+
a: {a.value}
+
b: {b.value}
+ +
+ )) + +export default () => ( + +
+      {``}
+    
+ {WithHandlersDemo} +
+); diff --git a/src/Dependencies.ts b/src/Dependencies.ts new file mode 100644 index 0000000..7db7065 --- /dev/null +++ b/src/Dependencies.ts @@ -0,0 +1,37 @@ + +type Dependency = { + _type: '@@ChainableComponentsDependency', + value: A, + equals: (l: A, r: A) => boolean +} + +function isDep(a: any): a is Dependency { + return a.type === '@@ChainableComponentsDependency' +} + +type Dependencies = (any | Dependency)[] + +export function equals(l: Dependencies, r: Dependencies): boolean { + return l.every((dep, i) => { + const rightDep = r[i] + if(isDep(dep)) { + if(isDep(rightDep)) { + return dep.equals(dep.value, rightDep.value) + } else { + return false; + } + } else { + return dep === rightDep; + } + }) +} + +export function itemEquals(l: any, r: any): boolean { + if(l && isDep(l) ) { + } + return l.equals(l.value, r.value) +} + +export function depEquals(l: Dependency, r: Dependency): boolean { + return l.equals(l.value, r.value) +} diff --git a/src/lib/withHandler.ts b/src/lib/withHandler.ts new file mode 100644 index 0000000..d8c4d5b --- /dev/null +++ b/src/lib/withHandler.ts @@ -0,0 +1,14 @@ +import { ChainableComponent } from '../ChainableComponent'; +import { withLifecycle } from './withLifecycle'; +import { equals } from '../Dependencies'; + +export function withHandler( + cb: F, + dependencies: any[] +): ChainableComponent { + return withLifecycle<[F, any[]]>({ + init: () => [cb, dependencies], + deriveContext: ([f, deps]) => + equals(deps, dependencies) ? [f, deps] : [cb, dependencies] + }).map(a => a[0]); +} diff --git a/src/lib/withLifecycle.ts b/src/lib/withLifecycle.ts index 435b082..948e2b3 100644 --- a/src/lib/withLifecycle.ts +++ b/src/lib/withLifecycle.ts @@ -10,39 +10,69 @@ import { * Configuration for the withLifecycle chainable component */ export type WithLifecycleOptions = { - componentDidMount: () => C; - componentWillUnmount?: (c: C) => void; + init: () => C; + componentDidMount?: (c: C) => C; + componentWillUnmount?: (c: C) => C; + deriveContext?: (c:C) => C; + componentWillUpdate?: (c: C) => C; shouldComponentUpdate?: (c: C) => boolean; }; -type WithLifecycleProps = RenderPropsProps, {}>; +type WithLifecycleProps = RenderPropsProps, C>; + +const iff = (a: A | undefined, f: (a:A) => void): void => a && f(a) + +const fold = (z: Z, a: A | undefined, f: (a:A) => Z): Z => a ? f(a) : z /** * A Render Prop component that mimics the react Component API */ export class WithLifecycle extends React.Component< WithLifecycleProps, - {} + { context: C } > { constructor(props: WithLifecycleProps) { super(props); + this.state = { + context: this.props.init() + } + } + + static getDerivedStateFromProps(nextProps: WithLifecycleProps, state: { context: C }) { + return nextProps.deriveContext ? { context: nextProps.deriveContext(state.context) } : state } componentDidMount() { - this.context = this.props.componentDidMount(); + iff(this.props.componentDidMount, dm => { + this.setState(s => { + context: dm(s.context) + }) + }) } + componentWillUnmount() { - this.props.componentWillUnmount && - this.props.componentWillUnmount(this.context); + iff(this.props.componentWillUnmount, wu => { + this.setState(s => { + context: wu(s.context) + }) + }) } shouldComponentUpdate() { - return this.props.shouldComponentUpdate - ? this.props.shouldComponentUpdate(this.context) - : true; + return fold(true, this.props.shouldComponentUpdate, scu => { + return scu(this.state.context) + }) + } + + UNSAFE_componentWillUpdate(nextProps: WithLifecycleProps) { + iff(nextProps.componentWillUpdate, wu => { + this.setState(s => { + context: wu(s.context) + }) + }) } render() { - return this.props.children({}); + return this.props.children(this.state.context); } } @@ -52,6 +82,6 @@ export class WithLifecycle extends React.Component< */ export function withLifecycle( options: WithLifecycleOptions -): ChainableComponent<{}> { +): ChainableComponent { return fromRenderProp>(WithLifecycle, options); }