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)