Skip to content

Commit

Permalink
Adding withHandlers
Browse files Browse the repository at this point in the history
  • Loading branch information
pfgray committed Apr 28, 2019
1 parent 8afbc7b commit a4e66e6
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 12 deletions.
32 changes: 32 additions & 0 deletions demo/demos/WithHandlersDemo.tsx
Original file line number Diff line number Diff line change
@@ -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}) => (
<div>
<div>a: {a.value} <button onClick={() => a.update(a.value + 1)}>+</button></div>
<div>b: {b.value} <button onClick={() => b.update(b.value + 1)}>+</button></div>
<button onClick={handler}>alert</button>
</div>
))

export default () => (
<Step title="WithHandlersDemo Demo">
<pre className='code-sample'>
{``}
</pre>
{WithHandlersDemo}
</Step>
);
37 changes: 37 additions & 0 deletions src/Dependencies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@

type Dependency<A> = {
_type: '@@ChainableComponentsDependency',
value: A,
equals: (l: A, r: A) => boolean
}

function isDep<T>(a: any): a is Dependency<T> {
return a.type === '@@ChainableComponentsDependency'
}

type Dependencies = (any | Dependency<any>)[]

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<A>(l: Dependency<A>, r: Dependency<A>): boolean {
return l.equals(l.value, r.value)
}
14 changes: 14 additions & 0 deletions src/lib/withHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { ChainableComponent } from '../ChainableComponent';
import { withLifecycle } from './withLifecycle';
import { equals } from '../Dependencies';

export function withHandler<F extends Function>(
cb: F,
dependencies: any[]
): ChainableComponent<F> {
return withLifecycle<[F, any[]]>({
init: () => [cb, dependencies],
deriveContext: ([f, deps]) =>
equals(deps, dependencies) ? [f, deps] : [cb, dependencies]
}).map(a => a[0]);
}
54 changes: 42 additions & 12 deletions src/lib/withLifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,39 +10,69 @@ import {
* Configuration for the withLifecycle chainable component
*/
export type WithLifecycleOptions<C> = {
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<C> = RenderPropsProps<WithLifecycleOptions<C>, {}>;
type WithLifecycleProps<C> = RenderPropsProps<WithLifecycleOptions<C>, C>;

const iff = <A>(a: A | undefined, f: (a:A) => void): void => a && f(a)

const fold = <A, Z>(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<C> extends React.Component<
WithLifecycleProps<C>,
{}
{ context: C }
> {
constructor(props: WithLifecycleProps<C>) {
super(props);
this.state = {
context: this.props.init()
}
}

static getDerivedStateFromProps<C>(nextProps: WithLifecycleProps<C>, 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<C>) {
iff(nextProps.componentWillUpdate, wu => {
this.setState(s => {
context: wu(s.context)
})
})
}

render() {
return this.props.children({});
return this.props.children(this.state.context);
}
}

Expand All @@ -52,6 +82,6 @@ export class WithLifecycle<C> extends React.Component<
*/
export function withLifecycle<C>(
options: WithLifecycleOptions<C>
): ChainableComponent<{}> {
): ChainableComponent<C> {
return fromRenderProp<WithLifecycleProps<C>>(WithLifecycle, options);
}

0 comments on commit a4e66e6

Please sign in to comment.