EDIT: with React Hooks, you can just use useContext to do this with no pain, this article is rendered of little value now, so is the library. I haven't found myself needing this at all.
A Little Context (lol)
Redux has been my home, will be my home for a lot of use cases. It's made life easy as a developer who's always had to single handedly manage projects of scale. But here's a myriad of use cases where you don't need Redux's magic or functionality. Sometimes you just need central state without prop drilling. I gave job interviews in the last year that required small take-home projects and I realised just how powerful the Context API can be when you don't need Redux/MobX et al. Only issue, Redux let me put everything in one place and elegantly select what I needed from it. With Consumers, I got stuck in situations where there were render props inside render props inside...you get the drift. If you're into functional programming, first thought in your mind is if only I could compose these. Let's look at some mildly problematic code to understand this.
import React, { Fragment } from "react";
import { render } from "react-dom";
import { __, map, prop } from "ramda";
import Drawer from 'drawer-component-from-wherever';
import List from 'list-component-from-wherever';
import Title from 'title-component-from-wherever';
/*
Note: the following is not the "right" way to initialise context values, you're
supposed to use a Provider and pass a value prop to it. If the Consumer finds
no matching parent Provider, only then it uses the arguments passed to
createContext as the initial value. This is a hypothetical example,
hence the shortcuts.
*/
const PoseContext = React.createContext('closed'); // is the drawer open or closed?
const CartContext = React.createContext([{
ids: idsFromSomewhere,
cartMap: objectFromSomewhereElse,
}]);
const App = () => (
<PoseContext.Consumer>
{pose => (
<Drawer pose={pose}>
<Title pose={pose}>Your Cart</Title>
<CartContext.Consumer>
{({ ids, cartMap }) => <List data={map(prop(__, cartMap), ids)} /> }
</CartContext.Consumer>
</Drawer>
)}
</PoseContext.Consumer>
);
render(<App />, document.getElementById('appRoot'));
Well, that doesn't look very ugly now. But imagine if instead of using ramda and offloading to another component, we had something like this in the CartContext's Consumer:
<CartContext.Consumer>
{({ ids, cartMap }) => (
<Fragment>
{ids.map((id) => {
const product = cartMap[id];
return (
<CartItem onClick={clickHandler} key={id}>
<Link route={`/products/${product.slug}/p/${product.id}`}>
<a>{product.name}</a>
</Link>
</CartItem>
);
})}
</Fragment>
)}
</CartContext.Consumer>;
Now imagine this, but with another Consumer called CouponConsumer to inject the app's Coupon related state. I would be terrified to look at Cart.js even if the culprit was me from 2 months ago. Enough banter, let's now be true to the title of this post and propose a solution to make neat code.
Adopting react-adopt (ok sorry no more)
The tiny library that saves the day.
pedronauck / react-adopt
๐ Compose render props components like a pro
๐ 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 this:
๐ก ย Solution
- Small. 0.7kb minified!
- Extremely Simple. Just a method!
React Adopt is a simple method that composes multiple render prop components, combining each prop result from your mapper.
๐ ย Demos
๐ป ย Usage
Install as project dependency:
$ yarn add react-adopt
Now you can useโฆ
import { adopt } from 'react-adopt';
const CombinedContext = adopt({
pose: <PoseContext.Consumer />,
cart: <CartContext.Consumer />,
});
const App = () => (
<CombinedContext>
{({ pose, cart: { ids, cartMap } }) => (
<Drawer pose={pose}>
<Title pose={pose}>Your Cart</Title>
<List data={map(prop(__, cartMap), ids)} />
</Drawer>
)}
</CombinedContext>
);
Neat, isn't it? We were able to compose two render prop components into a single one, and we could do the same thing with three or four. While Context Consumers are a great demo for this, we can utilise this neat trick for all render prop components and make our code more understandable and organised.
I'm trying to make it a habit to write every week, follow me if you think you want more of these tiny tricks that I picked up along my front end journey.
Top comments (0)