diff --git a/.editorconfig b/.editorconfig index f699a73..857d858 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,6 +1,7 @@ * indent_style = space indent_size = 2 +trim_trailing_whitespace = true [*.{ts,tsx}] quote_type = single diff --git a/demo/demos/AllDemo.tsx b/demo/demos/AllDemo.tsx index 6947bfd..bec6fa2 100644 --- a/demo/demos/AllDemo.tsx +++ b/demo/demos/AllDemo.tsx @@ -1,10 +1,10 @@ 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}), @@ -12,7 +12,7 @@ export const WithStateDemo = withState({initial: 5}), withState({initial: 8}) ]) - .ap(([a, b, c, d, e, f]) => ( + .render(([a, b, c, d, e, f]) => (
{/* a.value is inferred as a string */}
a: {a.value}
@@ -27,11 +27,11 @@ export const WithStateDemo = )); export default () => ( - +
-      {`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}),
@@ -39,7 +39,7 @@ all([
   withState({initial: 5}),
   withState({initial: 8})
 ])
-.ap(([a, b, c, d, e, f]) => (
+.render(([a, b, c, d, e, f]) => (
   
{/* a.value is inferred as a string */}
a: {a.value}
@@ -53,8 +53,6 @@ all([
));`}
- {WithStateDemo} + {AllDemo}
); - - diff --git a/demo/demos/ContextDemo.tsx b/demo/demos/ContextDemo.tsx index cba90f2..eaa46af 100644 --- a/demo/demos/ContextDemo.tsx +++ b/demo/demos/ContextDemo.tsx @@ -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 ( @@ -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 ( diff --git a/demo/demos/DoDemo.tsx b/demo/demos/DoDemo.tsx deleted file mode 100644 index cfec38b..0000000 --- a/demo/demos/DoDemo.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import * as React from 'react'; -import { withState } from '../../src/lib/withState'; -import { all, Do, ChainableComponent } from '../../src/ChainableComponent'; -import Step from '../Step'; - -export const DoDemo = () => - Do(function*() { - const a = yield withState({initial: 'string value'}); - const b = yield withState({initial: 8432}); - return [a, b]; - }) - .ap(([a, b]) => ( -
- {/* a.value is inferred as a string */} -
a: {a.value}
- - {/* b.value through f.value is inferred as a number */} -
b: {b.value}
-
- )); - -export default () => ( - -
-      {`import { withState, all } from 'chainable-components';`}
-    
- -
-); - - diff --git a/demo/demos/FromApDemo.tsx b/demo/demos/FromRenderDemo.tsx similarity index 50% rename from demo/demos/FromApDemo.tsx rename to demo/demos/FromRenderDemo.tsx index ea3d26c..f9fabdc 100644 --- a/demo/demos/FromApDemo.tsx +++ b/demo/demos/FromRenderDemo.tsx @@ -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
Applied: {ap(5)}
} )) - .ap(a => ( -
wait, wuuuut: {a}
- )); + .render(a => ( +
test: {a}
+ )); export default () => (
       {`import { withState, all } from 'chainable-components';`}
     
- {WithStateDemo} + {FromRenderDemo}
); - + diff --git a/demo/demos/ReactRouterDemo.tsx b/demo/demos/ReactRouterDemo.tsx index fb6a7c3..2893f76 100644 --- a/demo/demos/ReactRouterDemo.tsx +++ b/demo/demos/ReactRouterDemo.tsx @@ -15,7 +15,7 @@ const ReactRouterDemoInner: React.SFC = () => ( {`import { Route } from 'react-router'; const withRoute = fromRenderProp(Route); -withRoute({}).ap( +withRoute({}).render( route => { return ( @@ -27,7 +27,7 @@ withRoute({}).ap( )`} - {withRoute({}).ap( + {withRoute({}).render( route => { return ( diff --git a/demo/demos/WithStateDemo.tsx b/demo/demos/WithStateDemo.tsx index 5b05b36..5183407 100644 --- a/demo/demos/WithStateDemo.tsx +++ b/demo/demos/WithStateDemo.tsx @@ -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 }) => (
Outer: {outer.value}
Inner: {inner.value}
@@ -25,7 +24,7 @@ withState({initial: 0}).chain(outer => ({inner, outer}) ) ) -.ap(({inner, outer}) => ( +.render(({inner, outer}) => (
Outer: {outer.value}
Inner: {inner.value}
@@ -35,14 +34,4 @@ withState({initial: 0}).chain(outer => {WithStateDemo} ); - // all([ - // withState({initial: 'yo'}), - // withState({initial: 16}) - // ]) - // .ap(([outer, inner]) => ( - //
- //
Outer: {outer.value}
- //
Inner: {inner.value}
- //
- // )); diff --git a/package.json b/package.json index 6f5af24..9c5fe5b 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/ChainableComponent.ts b/src/ChainableComponent.ts index ebf3df7..a614ac0 100644 --- a/src/ChainableComponent.ts +++ b/src/ChainableComponent.ts @@ -1,21 +1,19 @@ import { createFactory, ReactNode } from 'react'; -// export type ComponentLike

= React.ComponentClass | React.StatelessComponent

; - /** * A composable wrapper around React effects. - * + * */ export type ChainableComponent = { /** * 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. @@ -24,6 +22,12 @@ export type ChainableComponent = { */ map(f: (a: A) => B): ChainableComponent; + /** + * Converts the value inside this Chainable Component. + * @param c Apply the function inside of c to the value inside of this Chainable Component + */ + ap(c: ChainableComponent<(a: A) => B>): ChainableComponent; + /** * 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 @@ -38,7 +42,7 @@ export type ChainableComponent = { * @type A represents the type of the contextual value of the Render Props component. */ export type RenderPropsProps = P & { - children: (a: A) => ReactNode + children: (a: A) => ReactNode, }; /** @@ -54,11 +58,11 @@ export type RenderPropsComponent = React.ComponentType = (f: (a: A) => ReactNode) => ReactNode; export function fromRenderProp

(Inner: RenderPropsComponent): (p: P) => ChainableComponent { - return p => fromAp(f => { + return p => fromRender(f => { const apply = createFactory>(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, }); }); } @@ -70,64 +74,68 @@ export function fromRenderProp

(Inner: RenderPropsComponent< */ export function fromNonStandardRenderProp

( renderMethod: string, - Inner: React.ComponentClass

ReactNode}> + Inner: React.ComponentClass

ReactNode }>, ): (p: P) => ChainableComponent { - return p => fromAp(f => { - const apply = createFactory

ReactNode}>(Inner); + return p => fromRender(f => { + const apply = createFactory

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(ap: (f: (a: A) => ReactNode) => ReactNode): ChainableComponent { +export function fromRender(render: (f: (a: A) => ReactNode) => ReactNode): ChainableComponent { return { - ap, + render, map(f: (a: A) => B): ChainableComponent { - const Mapped: Applied = g => this.ap(a => g(f(a))); - return fromAp(Mapped); + const Mapped: Applied = g => this.render(a => g(f(a))); + return fromRender(Mapped); + }, + ap(c: ChainableComponent<(a: A) => B>): ChainableComponent { + const Apped: Applied = g => this.render(a => c.render(f => g(f(a)))); + return fromRender(Apped); }, chain(f: (a: A) => ChainableComponent): ChainableComponent { - const FlatMapped: Applied = g => this.ap(a => f(a).ap(g)); - return fromAp(FlatMapped); - } + const FlatMapped: Applied = g => this.render(a => f(a).render(g)); + return fromRender(FlatMapped); + }, }; } type CC = ChainableComponent; +function all( + values: [CC, CC, CC, CC, CC, CC, CC, CC, CC]): CC<[T1, T2, T3, T4, T5, T6, T7, T8, T9]>; +function all( + values: [CC, CC, CC, CC, CC, CC, CC, CC]): CC<[T1, T2, T3, T4, T5, T6, T7, T8]>; +function all(values: [CC, CC, CC, CC, CC, CC, CC]): CC<[T1, T2, T3, T4, T5, T6, T7]>; +function all(values: [CC, CC, CC, CC, CC, CC]): CC<[T1, T2, T3, T4, T5, T6]>; +function all(values: [CC, CC, CC, CC, CC]): CC<[T1, T2, T3, T4, T5]>; +function all(values: [CC, CC, CC, CC]): CC<[T1, T2, T3, T4]>; +function all(values: [CC, CC, CC]): CC<[T1, T2, T3]>; +function all(values: [CC, CC]): CC<[T1, T2]>; +function all(values: (CC)[]): CC; +function all(values: CC[]) { + return values.reduce((aggOp: CC, aOp: CC) => { + 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): ChainableComponent { - return fromAp(f => f(a)); - } - + return fromRender(f => f(a)); + }, + all, }; - -export function all(values: [CC, CC, CC, CC, CC, CC, CC, CC, CC]): CC<[T1, T2, T3, T4, T5, T6, T7, T8, T9]> -export function all(values: [CC, CC, CC, CC, CC, CC, CC, CC]): CC<[T1, T2, T3, T4, T5, T6, T7, T8]> -export function all(values: [CC, CC, CC, CC, CC, CC, CC]): CC<[T1, T2, T3, T4, T5, T6, T7]> -export function all(values: [CC, CC, CC, CC, CC, CC]): CC<[T1, T2, T3, T4, T5, T6]> -export function all(values: [CC, CC, CC, CC, CC]): CC<[T1, T2, T3, T4, T5]> -export function all(values: [CC, CC, CC, CC]): CC<[T1, T2, T3, T4]> -export function all(values: [CC, CC, CC]): CC<[T1, T2, T3]> -export function all(values: [CC, CC]): CC<[T1, T2]> -export function all(values: (CC)[]): CC -export function all(values: CC[]) { - return values.reduce((aggOp: CC, aOp: CC) => - aggOp.chain((agg: any[]) => ( - aOp.map(a => agg.concat([a])) - ) - ), ChainableComponent.of([])); -} - - -// [].concat() \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index fd4596b..4ac844b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,21 +1,19 @@ -import { +import { ChainableComponent, - RenderPropsProps, - RenderPropsComponent, + fromRender, fromRenderProp, - fromAp, - all, + RenderPropsComponent, + RenderPropsProps, } from './ChainableComponent'; -import { withState, WithState } from './lib/withState'; import { withPromise, WithPromise } from './lib/withPromise'; +import { withState, WithState } from './lib/withState'; export { ChainableComponent, RenderPropsProps, RenderPropsComponent, fromRenderProp, - fromAp, - all, + fromRender, withState, WithState, withPromise, WithPromise, -}; \ No newline at end of file +}; diff --git a/src/lib/withPromise.tsx b/src/lib/withPromise.tsx index 89d7b81..304f33c 100644 --- a/src/lib/withPromise.tsx +++ b/src/lib/withPromise.tsx @@ -1,25 +1,25 @@ -import { fromRenderProp, ChainableComponent, RenderPropsProps } from '../ChainableComponent'; import * as React from 'react'; +import { ChainableComponent, fromRenderProp, RenderPropsProps } from '../ChainableComponent'; /** * The state of the WithProps component. * It can either be loading, or have data available. */ export type WithPromiseState = { - loading: true + loading: true, } | { loading: false, - data: A + data: A, }; /** - * The type of options that WithPromise expects + * The type of options that WithPromise expects */ export type WithPromiseOptions = { /** * A method that returns a Promise. this method will be invoked once when the component is mounted. */ - get: () => Promise + get: () => Promise, }; /** @@ -28,19 +28,19 @@ export type WithPromiseOptions = { export type WithPromiseProps = RenderPropsProps, WithPromiseState>; /** - * A Render Prop component that encapsulates the state around resolving a Promise which is + * A Render Prop component that encapsulates the state around resolving a Promise which is * requested when this component mounts. */ export class WithPromise extends React.Component, WithPromiseState> { state: WithPromiseState = { - loading: true + loading: true, }; componentDidMount() { this.props.get().then(data => { this.setState(() => ({ loading: false, - data + data, })); }); } @@ -48,13 +48,13 @@ export class WithPromise extends React.Component, WithPro render() { return this.props.children(this.state); } -}; +} /** - * Builds a chainable component that encapsulates the state around resolving a Promise which is + * Builds a chainable component that encapsulates the state around resolving a Promise which is * requested when this component mounts. * @param opts Options for this chainable component which provide a method that returns */ export function withPromise(opts: WithPromiseOptions): ChainableComponent> { return fromRenderProp, WithPromiseState>(WithPromise)(opts); -}; +} diff --git a/src/lib/withState.ts b/src/lib/withState.ts index ea93161..5b6c773 100644 --- a/src/lib/withState.ts +++ b/src/lib/withState.ts @@ -1,11 +1,11 @@ import * as React from 'react'; -import { fromRenderProp, ChainableComponent, RenderPropsProps } from '../ChainableComponent'; +import { ChainableComponent, fromRenderProp, RenderPropsProps } from '../ChainableComponent'; /** * The state used by the WithState render prop. */ export type WithStateState = { - value: A + value: A, }; /** @@ -13,21 +13,21 @@ export type WithStateState = { */ export type WithStateContext = { value: A, - update: (a: A) => void + update: (a: A) => void, }; /** - * The options to pass + * The options to pass */ export type WithStateOptions = { - initial: A + initial: A, }; export type WithStateProps = RenderPropsProps, WithStateContext>; export class WithState extends React.Component, WithStateState> { state: WithStateState = { - value: this.props.initial + value: this.props.initial, }; constructor(props: WithStateProps) { @@ -37,14 +37,14 @@ export class WithState extends React.Component, WithStateSt update(a: A) { this.setState({ - value: a + value: a, }); } render() { return this.props.children({ value: this.state.value, - update: this.update + update: this.update, }); } } diff --git a/tslint.json b/tslint.json index 20d18f6..8e38ac9 100644 --- a/tslint.json +++ b/tslint.json @@ -5,7 +5,9 @@ ], "jsRules": {}, "rules": { + "interface-over-type-literal": false, "array-type": false, + "arrow-parens": false, "interface-name": { "options": "never-prefix" },