Skip to content

Commit

Permalink
Re-doing readme
Browse files Browse the repository at this point in the history
  • Loading branch information
pfgray committed Aug 31, 2018
1 parent af1e7e1 commit c1dc77f
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 104 deletions.
155 changes: 51 additions & 104 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,120 +1,67 @@
# Chainable Components
_Make your render props composable!_
_A composable API for reusable React code._

From:
Chain together reusable React components:
```jsx
const WithTwoState = props => {
return (
<WithState initial={0}>
{outer => (
<WithState initial={outer + 5}>
{inner => (
props.children({inner, outer})
)}
</WithState>
)
</WithState>
);
}
```
To:
```jsx
const withTwoState =
withState({initial: 0}).chain(outer =>
withState({initial: outer + 5}).map(inner =>
({inner, outer})
)
withState(0).chain(outer =>
withState(outer.value + 5).map(inner =>
({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>
</div>
));
```

### Converting a Render Prop component to a Chainable:
Here's an example of a render prop `WithPromise`:
```jsx
<WithPromise get={() => fetchUser(1234)}>
{user => (
<div>Hello, {user.username}!</div>
)}
</WithPromise>
```
You provide a function that returns a promise via the `get` attribute, then it returns the value in that promise via the children callback. In this case, the `fetchUser` function returns a promise that will return a `User`, so a `User` is supplied to the callback.
You can convert this existing Render Prop component to a chainable component with the `fromRenderProp` function:
```jsx
const withPromise = fromRenderProp(WithPromise);
```
`withState` is now a function that takes a configuration object (which would have been props to the render prop function), and returns a chainable component:
```jsx
const userChainable: ChainableComponent<User> = withPromise({get: () => fetchUser(1234)});
```
Transform HOCs and Render Props to chainables and back:

Since the `get` function will return a promise of `User`, the contextual value inside of this chainable component is `User`, so `userChainable`’s type is `ChainableComponent<User>` (said, “chainable component of user”).
![Chainable pipeline](docsSrc/chainable-pipeline.png?raw=true "Chainable pipeline")

### Rendering a Chainable Component:
Since the ”wrapped” value’s type is `User`, the `ap` method takes a function which takes a user, and returns the rendered output:
Example:
```jsx
userChainable.render(user => (
<div>Hello, {user.username}!</div>
import { Route } from 'react-router';
import { connect } from 'react-redux';

const withConnect = fromHigherOrderComponent(connect(mapState, mapDispatch));
const withRoute = fromRenderProp(Route);

// withConnect and withRoute are now chainable!
const withConnectAndRoute =
withConnect.chain(storeProps =>
withRoute.map(route => ({
store: storeProps,
path: route.history.location.pathname
})));

// then render it!
withConnectAndRoute.render(({store, path}) => (
<div>
current path is: {path}
store contains: {store.users}
</div>
));
```

All together, this looks like:
```jsx
withPromise({get: () => fetchUser(1234)})
.render(user => (
<div>{user.id} - {user.username}</div>
))
```
Which is actually quite similar to the render prop version:
```jsx
<WithPromise get={() => fetchUser(1234)}>
{user => (
<div>{user.id} - {user.username}</div>
// or convert it back render prop:
const ConnectAndRoute = withConnectAndRoute.toRenderProp();
<ConnectAndRoute>
{({store, path}) => (
<div>
current path is: {path}
store contains: {store.users}
</div>
)}
</WithPromise>
```
Why would we go through the trouble of converting a Render Prop to a Chainable Component? The answer is the additional methods a chainable component has, `map` and `chain`.
### Mapping values inside a Chainable Component
Suppose we didn’t care about any information about the user at all, only their role, because we wanted to display something to the user, but only if they were an admin. We could map the user value inside our hoc into a boolean, and then use the boolean when we actually apply the chainable component. For instance,
```jsx
withPromise({get: () => fetchUser(1234)})
.map(user => user.role === 'Administrator')
.render(isAdmin => (
isAdmin ? (<div>secret plans...</div>) :
<div>Access denied!</div>
));
```
### Composing Chainable Components
Chainable components can be composed, or “chained” easily using the chain method. `chain` is very similar to `map`, except that the function parameter returns another chainable component. This allows you to combine chainables, and use the output of one as input to the other.
<ConnectAndRoute>

Let’s suppose that you wanted to fetch a user, and when the user loaded, “fade in” the ui so that it looks smooth. You could add the fade in styles to the `WithPromise` render prop, but then it would be applied to all instances where `WithPromise` is used. A better approach, would be to build a separate chainable component, and then compose the two together with `chain`:
// or convert it back to a HOC:
const connectAndRouteHoc = withConnectAndRoute.toHigherOrderComponent(p => p);

Let's suppose we have a `fadeIn` chainable component, which just applies styles based on configuration passed to it:
```jsx
fadeIn({duration: 500, delay: 0})
.render(() => (
<div>This is smooth!</div>
));
```
Composing the `withPromise` and `fadeIn` chainable components would look like:
```jsx
const fadeIn = buildChainable(FadeIn);

withPromise({get: () => fetchUser(1234)})
.chain(user =>
fadeIn({duration: 500, delay: 0})
.map(() => user))
.render(user => (
<div>{user.id} - {user.username}</div>
))
connectAndRouteHoc(({store, path}) => (
<div>
current path is: {path}
store contains: {store.users}
</div>
));
```

With the addition of `map` and `chain`, our Render Prop components can now enjoy the composablity of HOC’s, but still keep the declarative nature of Render Props!
Binary file added docsSrc/chainable-pipeline.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit c1dc77f

Please sign in to comment.