:sunglasses: React Adopt - Compose render props components like a pro
📜 Table of content
🧐 Why
Render Props are the new hype of React's ecosystem, that's a fact. So, when you need to use more than one render props component together, this can be boring and generate something called a "render props callback hell", like that:
💡 Solution
- Small. 0.7kb minified!
- Extremely Simple. Just a method!
React Adopt is just a simple method that you can compose your components and return just one component that will be a render prop component that combining each prop result from your mapper.
📟 Demos
💻 Usage
Install as project dependency:
$ yarn add react-adopt
Now you can use adopt to compose your components. See bellow an example using the awesome react-powerplug:
Working with new Context api
One of use case that React Adopt can fit perfectly is when you need to use new React's context api that use render props to create some context:
import React from 'react'
import { adopt } from 'react-adopt'
const ThemeContext = React.createContext('light')
const UserContext = React.createContext({ name: 'John' })
const Context = adopt({
theme: <ThemeContext.Consumer />,
user: <UserContext.Consumer />,
})
<Context>
{({ theme, user }) => }
</Context>
See this demo for a better comprehension
Custom render and retrieving props from composed
Some components don't use the prop called children
to make work render props. For cases like that, when you define your mapper you can pass a simple function as mapper value that will return your component, instead of a jsx element. This function will receive a prop render
that will be responsible to make render, the props passed on Composed
component and the previous values from each mapper. See an example:
import { adopt } from 'react-adopt'
import MyCustomRenderProps from 'my-custom-render-props'
const Composed = adopt({
custom: ({ render }) => <MyCustomRenderProps render={render} />
})
<Composed>
{({ custom }) => (
<div>{custom.value}</div>
)}
</Composed>
And as I said above, you can retrieve the properties passed to the composed component using that way too:
import { adopt } from 'react-adopt'
import { Value } from 'react-powerplug'
const Composed = adopt({
greet: ({ initialGreet, render }) => (
<Value initial={initialGreet}>{render}</Value>
)
})
<Composed initialGreet="Hi">
{({ greet }) => (
<div>{greet.value}</div>
)}
</Composed>
And get previous mapper results as prop for compose:
import { adopt } from 'react-adopt'
import { User, Cart, ShippingRate } from 'my-containers'
const Composed = adopt({
cart: <Cart />,
user: <User />,
shippingRates: ({ user, cart, render }) => (
<ShippingRate zipcode={user.zipcode} items={cart.items}>
{render}
</ShippingRate>
)
})
<Composed>
{({ cart, user, shippingRates }) => }
</Composed>
Leading with multiples params
Some render props components return multiple arguments in the children function instead of single one, a simple example in the new Query and Mutation component from react-apollo
. In that case, what you can do is a arbitrary render with render
prop using you map value as a function:
import { adopt } from 'react-adopt'
import { Mutation } from 'react-apollo'
const ADD_TODO =
const addTodo = ({ render }) => (
<Mutation mutation={ADD_TODO}>
{/* that's is arbitrary render where you will pass your two arguments into single one */}
{(mutation, result) => render({ mutation, result })}
</Mutation>
)
const Composed = adopt({
addTodo,
})
const App = () => (
<Compose>
{({ addTodo: { mutation, result } }) => /* ... */}
</Compose>
)
See this demo for a complete explanation about that.
Typescript support
React adopt has a fully typescript support when you need to type the composed component:
import * as React from 'react'
import { adopt } from 'react-adopt'
import { Value } from 'react-powerplug'
interface RenderProps {
foo: { value: string }
}
interface Props {
tor: string
}
const foo = ({ tor, render }) => (
<Value initial="foo">{render}</Value>
)
const Composed = adopt<RenderProps, Props>({
foo,
})
<Composed tor="tor">
{({ foo, bar }) => (
<div>{foo.value}</div>
)}
</Composed>
Inline composition
If you dont care about typings and need something more easy and quick, you can choose to use a inline composition by importing <Adopt>
component and passing your mapper as prop:
import React from 'react'
import { Adopt } from 'react-adopt'
import { Value } from 'react-powerplug'
const mapper = {
greet: <Value initial="Hi" />,
name: <Value initial="John" />
}
<Adopt mapper={mapper}>
{({ greet, name }) => }
</Adopt>
🕺 Contribute
- Fork this repository to your own GitHub account and then clone it to your local device
- Install dependencies using Yarn:
yarn install
- Make the necessary changes and ensure that the tests are passing using
yarn test
- Send a pull request 🙌