Skip to content

Commit

Permalink
Merge pull request #4 from pfgray/ap_refactor
Browse files Browse the repository at this point in the history
Ap refactor
  • Loading branch information
pfgray authored Jun 23, 2018
2 parents 91e57fc + 86a60d1 commit 5b8a489
Show file tree
Hide file tree
Showing 13 changed files with 106 additions and 140 deletions.
1 change: 1 addition & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
*
indent_style = space
indent_size = 2
trim_trailing_whitespace = true

[*.{ts,tsx}]
quote_type = single
20 changes: 9 additions & 11 deletions demo/demos/AllDemo.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import * as React from 'react';
import { withState } from '../../src/lib/withState';
import { all } from '../../src/ChainableComponent';
import { ChainableComponent } from '../../src/ChainableComponent';
import Step from '../Step';

export const WithStateDemo =
all([
export const AllDemo =
ChainableComponent.all([
withState({initial: 'string value'}),
withState({initial: 1}),
withState({initial: 2}),
withState({initial: 3}),
withState({initial: 5}),
withState({initial: 8})
])
.ap(([a, b, c, d, e, f]) => (
.render(([a, b, c, d, e, f]) => (
<div>
{/* a.value is inferred as a string */}
<div>a: {a.value} <button onClick={() => a.update(a.value + 1)}>+</button></div>
Expand All @@ -27,19 +27,19 @@ export const WithStateDemo =
));

export default () => (
<Step title="withState Demo">
<Step title="all Demo">
<pre className='code-sample'>
{`import { withState, all } from 'chainable-components';
{`import { withState, ChainableComponent } from 'chainable-components';
all([
ChainableComponent.all([
withState({initial: 'string value'}),
withState({initial: 1}),
withState({initial: 2}),
withState({initial: 3}),
withState({initial: 5}),
withState({initial: 8})
])
.ap(([a, b, c, d, e, f]) => (
.render(([a, b, c, d, e, f]) => (
<div>
{/* a.value is inferred as a string */}
<div>a: {a.value} <button onClick={() => a.update(a.value + 1)}>+</button></div>
Expand All @@ -53,8 +53,6 @@ all([
</div>
));`}
</pre>
{WithStateDemo}
{AllDemo}
</Step>
);


4 changes: 2 additions & 2 deletions demo/demos/ContextDemo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const { Consumer, Provider } = React.createContext("Default Value");
const withContext = fromRenderProp(Consumer);

const DisplayContext =
withContext({ children: () => 'hmm, this should be necessary' }).ap(
withContext({ children: () => 'hmm, this should be necessary' }).render(
context => {
return (
<span>
Expand Down Expand Up @@ -35,7 +35,7 @@ const { Consumer, Provider } = React.createContext("Default Value");
const withStringContext = fromRenderProp(Consumer);
const DisplayContext =
withStringContext({ children: a => '' }).ap(
withStringContext({ children: a => '' }).render(
context => {
return (
<span>
Expand Down
31 changes: 0 additions & 31 deletions demo/demos/DoDemo.tsx

This file was deleted.

19 changes: 9 additions & 10 deletions demo/demos/FromApDemo.tsx → demo/demos/FromRenderDemo.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
import * as React from 'react';
import { withState } from '../../src/lib/withState';
import { all, fromAp } from '../../src/ChainableComponent';
import { fromRender } from '../../src/ChainableComponent';
import Step from '../Step';

export const WithStateDemo =
fromAp((ap: (a: number) => React.ReactNode) => (
function() {
export const FromRenderDemo =
fromRender((ap: (a: number) => React.ReactNode) => (
function () {
return <div>Applied: {ap(5)}</div>
}
))
.ap(a => (
<div>wait, wuuuut: {a}</div>
));
.render(a => (
<div>test: {a}</div>
));

export default () => (
<Step title="FromAp Demo">
<pre className='code-sample'>
{`import { withState, all } from 'chainable-components';`}
</pre>
{WithStateDemo}
{FromRenderDemo}
</Step>
);


4 changes: 2 additions & 2 deletions demo/demos/ReactRouterDemo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const ReactRouterDemoInner: React.SFC = () => (
{`import { Route } from 'react-router';
const withRoute = fromRenderProp(Route);
withRoute({}).ap(
withRoute({}).render(
route => {
return (
<span>
Expand All @@ -27,7 +27,7 @@ withRoute({}).ap(
)`}
</pre>
<Router history={customHistory}>
{withRoute({}).ap(
{withRoute({}).render(
route => {
return (
<span>
Expand Down
15 changes: 2 additions & 13 deletions demo/demos/WithStateDemo.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import * as React from 'react';
import { withState } from '../../src/lib/withState';
import Step from '../Step';
// import { all } from '../src/ChainableComponent';

export const WithStateDemo =
withState({ initial: 0 }).chain(outer =>
withState({ initial: 16 }).map(inner =>
({ inner, outer })
)
).ap(({ inner, outer }) => (
).render(({ inner, outer }) => (
<div>
<div>Outer: {outer.value} <button onClick={() => outer.update(outer.value + 1)}>+</button></div>
<div>Inner: {inner.value} <button onClick={() => inner.update(inner.value + 1)}>+</button></div>
Expand All @@ -25,7 +24,7 @@ withState({initial: 0}).chain(outer =>
({inner, outer})
)
)
.ap(({inner, outer}) => (
.render(({inner, outer}) => (
<div>
<div>Outer: {outer.value} <button onClick={() => outer.update(outer.value + 1)}>+</button></div>
<div>Inner: {inner.value} <button onClick={() => inner.update(inner.value + 1)}>+</button></div>
Expand All @@ -35,14 +34,4 @@ withState({initial: 0}).chain(outer =>
{WithStateDemo}
</Step>
);
// all([
// withState({initial: 'yo'}),
// withState({initial: 16})
// ])
// .ap(([outer, inner]) => (
// <div>
// <div>Outer: {outer.value} <button onClick={() => outer.update(outer.value + 1)}></button></div>
// <div>Inner: {inner.value} <button onClick={() => inner.update(inner.value + 1)}></button></div>
// </div>
// ));

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
"url": "https://paulgray.net/"
},
"scripts": {
"lint": "tslint --project tsconfig-build.json",
"lint-fix": "tslint --project tsconfig-build.json --fix",
"build": "npm-run-all clean build:ts",
"build:ts": "tsc -p tsconfig-build.json",
"bundle": "rm -rf docs/ && webpack --config webpack/bundle.ts",
Expand Down
94 changes: 51 additions & 43 deletions src/ChainableComponent.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
import { createFactory, ReactNode } from 'react';

// export type ComponentLike<P = {}> = React.ComponentClass<P, any> | React.StatelessComponent<P>;

/**
* A composable wrapper around React effects.
*
*
*/
export type ChainableComponent<A> = {

/**
* Renders this chainable into a ReactNode, which can be embedded inside the render
* method of another component.
* @param f A function which is used to render the contextual value.
* @param f A function which is used to render the contextual value.
* This method returns another ReactNode, possibly wrapped with
* additional functionality.
*/
ap(f: (a: A) => ReactNode): ReactNode;
render(f: (a: A) => ReactNode): ReactNode;

/**
* Converts the value inside this Chainable Component.
Expand All @@ -24,6 +22,12 @@ export type ChainableComponent<A> = {
*/
map<B>(f: (a: A) => B): ChainableComponent<B>;

/**
* Converts the value inside this Chainable Component.
* @param c Apply the function inside of c to the value inside of this Chainable Component
*/
ap<B>(c: ChainableComponent<(a: A) => B>): ChainableComponent<B>;

/**
* Composes or 'chains' another Chainable Component along with this one.
* @param f A function which is provided the contextual value, and returns a chainable component
Expand All @@ -38,7 +42,7 @@ export type ChainableComponent<A> = {
* @type A represents the type of the contextual value of the Render Props component.
*/
export type RenderPropsProps<P, A> = P & {
children: (a: A) => ReactNode
children: (a: A) => ReactNode,
};

/**
Expand All @@ -54,11 +58,11 @@ export type RenderPropsComponent<P, A> = React.ComponentType<RenderPropsProps<P,
type Applied<A> = (f: (a: A) => ReactNode) => ReactNode;

export function fromRenderProp<P extends object, A>(Inner: RenderPropsComponent<P, A>): (p: P) => ChainableComponent<A> {
return p => fromAp(f => {
return p => fromRender(f => {
const apply = createFactory<RenderPropsProps<P, A>>(Inner as any);
return apply({
...(p as any), // todo: we have any until https://github.com/Microsoft/TypeScript/pull/13288 is merged
children: f
children: f,
});
});
}
Expand All @@ -70,64 +74,68 @@ export function fromRenderProp<P extends object, A>(Inner: RenderPropsComponent<
*/
export function fromNonStandardRenderProp<P extends object, A>(
renderMethod: string,
Inner: React.ComponentClass<P & {[render: string]: (a:A) => ReactNode}>
Inner: React.ComponentClass<P & { [render: string]: (a: A) => ReactNode }>,
): (p: P) => ChainableComponent<A> {
return p => fromAp(f => {
const apply = createFactory<P & {[renderMethod: string]: (a:A) => ReactNode}>(Inner);
return p => fromRender(f => {
const apply = createFactory<P & { [renderMethod: string]: (a: A) => ReactNode }>(Inner);
return apply({
...(p as any),
[renderMethod]: f
[renderMethod]: f,
});
});
}

/**
* Converts an apply function to a ChainableComponent
* @param ap
* @param render
*/
export function fromAp<A>(ap: (f: (a: A) => ReactNode) => ReactNode): ChainableComponent<A> {
export function fromRender<A>(render: (f: (a: A) => ReactNode) => ReactNode): ChainableComponent<A> {
return {
ap,
render,
map<B>(f: (a: A) => B): ChainableComponent<B> {
const Mapped: Applied<B> = g => this.ap(a => g(f(a)));
return fromAp(Mapped);
const Mapped: Applied<B> = g => this.render(a => g(f(a)));
return fromRender(Mapped);
},
ap<B>(c: ChainableComponent<(a: A) => B>): ChainableComponent<B> {
const Apped: Applied<B> = g => this.render(a => c.render(f => g(f(a))));
return fromRender(Apped);
},
chain<B>(f: (a: A) => ChainableComponent<B>): ChainableComponent<B> {
const FlatMapped: Applied<B> = g => this.ap(a => f(a).ap(g));
return fromAp(FlatMapped);
}
const FlatMapped: Applied<B> = g => this.render(a => f(a).render(g));
return fromRender(FlatMapped);
},
};
}

type CC<A> = ChainableComponent<A>;

function all<T1, T2, T3, T4, T5, T6, T7, T8, T9>(
values: [CC<T1>, CC<T2>, CC<T3>, CC<T4>, CC<T5>, CC<T6>, CC<T7>, CC<T8>, CC<T9>]): CC<[T1, T2, T3, T4, T5, T6, T7, T8, T9]>;
function all<T1, T2, T3, T4, T5, T6, T7, T8>(
values: [CC<T1>, CC<T2>, CC<T3>, CC<T4>, CC<T5>, CC<T6>, CC<T7>, CC<T8>]): CC<[T1, T2, T3, T4, T5, T6, T7, T8]>;
function all<T1, T2, T3, T4, T5, T6, T7>(values: [CC<T1>, CC<T2>, CC<T3>, CC<T4>, CC<T5>, CC<T6>, CC<T7>]): CC<[T1, T2, T3, T4, T5, T6, T7]>;
function all<T1, T2, T3, T4, T5, T6>(values: [CC<T1>, CC<T2>, CC<T3>, CC<T4>, CC<T5>, CC<T6>]): CC<[T1, T2, T3, T4, T5, T6]>;
function all<T1, T2, T3, T4, T5>(values: [CC<T1>, CC<T2>, CC<T3>, CC<T4>, CC<T5>]): CC<[T1, T2, T3, T4, T5]>;
function all<T1, T2, T3, T4>(values: [CC<T1>, CC<T2>, CC<T3>, CC<T4>]): CC<[T1, T2, T3, T4]>;
function all<T1, T2, T3>(values: [CC<T1>, CC<T2>, CC<T3>]): CC<[T1, T2, T3]>;
function all<T1, T2>(values: [CC<T1>, CC<T2>]): CC<[T1, T2]>;
function all<T>(values: (CC<T>)[]): CC<T[]>;
function all(values: CC<any>[]) {
return values.reduce((aggOp: CC<any[]>, aOp: CC<any>) => {
return aggOp.ap(aOp.map(a => {
const g: (a: any[]) => any = agg => agg.concat([a]);
return g;
}));
}, ChainableComponent.of([]));
}

export const ChainableComponent = {
/**
* Wraps any value 'A' into a chainable component.
* @param a the value that provides the context.
*/
of<A>(a: A): ChainableComponent<A> {
return fromAp(f => f(a));
}

return fromRender(f => f(a));
},
all,
};

export function all<T1, T2, T3, T4, T5, T6, T7, T8, T9>(values: [CC<T1>, CC<T2>, CC<T3>, CC<T4>, CC<T5>, CC<T6>, CC<T7>, CC<T8>, CC<T9>]): CC<[T1, T2, T3, T4, T5, T6, T7, T8, T9]>
export function all<T1, T2, T3, T4, T5, T6, T7, T8>(values: [CC<T1>, CC<T2>, CC<T3>, CC<T4>, CC<T5>, CC<T6>, CC<T7>, CC<T8>]): CC<[T1, T2, T3, T4, T5, T6, T7, T8]>
export function all<T1, T2, T3, T4, T5, T6, T7>(values: [CC<T1>, CC<T2>, CC<T3>, CC<T4>, CC<T5>, CC<T6>, CC<T7>]): CC<[T1, T2, T3, T4, T5, T6, T7]>
export function all<T1, T2, T3, T4, T5, T6>(values: [CC<T1>, CC<T2>, CC<T3>, CC<T4>, CC<T5>, CC<T6>]): CC<[T1, T2, T3, T4, T5, T6]>
export function all<T1, T2, T3, T4, T5>(values: [CC<T1>, CC<T2>, CC<T3>, CC<T4>, CC<T5>]): CC<[T1, T2, T3, T4, T5]>
export function all<T1, T2, T3, T4>(values: [CC<T1>, CC<T2>, CC<T3>, CC<T4>]): CC<[T1, T2, T3, T4]>
export function all<T1, T2, T3>(values: [CC<T1>, CC<T2>, CC<T3>]): CC<[T1, T2, T3]>
export function all<T1, T2>(values: [CC<T1>, CC<T2>]): CC<[T1, T2]>
export function all<T>(values: (CC<T>)[]): CC<T[]>
export function all(values: CC<any>[]) {
return values.reduce((aggOp: CC<any[]>, aOp: CC<any>) =>
aggOp.chain((agg: any[]) => (
aOp.map(a => agg.concat([a]))
)
), ChainableComponent.of([]));
}


// [].concat()
Loading

0 comments on commit 5b8a489

Please sign in to comment.