Epitath: In memoriam React Render Props and HOCs
Reusability is a must in React world, after mixins were considered harmful two big approaches got in to the scene: Render Props and HOCs.
Both created a lot of discussion about their limitations in the community. Render Props greatly avoid indirection but fail a bit in composability — although you can use something like Compose
you can’t still have a more explicit way to pass the props each Render Props take or not render another Render Props before you get a value from a previous one without adding some more boilerplate –and function HOCs have a great composability but fail in too much indirection. “Tradeoffs” one may say. But what if we could come up with a third way that would bring together the best from these approaches?
But what if we could come up with a third way that would bring together the best from these approaches?
Here we present you: Epitath, in memoriam HOCs and Render Props
“How did you come up with this? Is this a new approach?”
The click for us came when we realized that this
<Query>
{({ data }) =>
<Mutation>
{({ mutate, result }) =>
<Form>
{({ values, handleChange }) => { ad infinitum }}
</Form>
}
</Mutation>
}
</Query>
Does not just recalls but it is a callback hell, which in other words, would allow us to do some kind of CPS. Like async and await. If you want to take a look on CPS concept ramifications take a look on this ReasonML thread about adding a CPS sugar.
“How’s this different from React Suspense?”
Different from React Suspense which throws a promise, evaluate it and give you back the result once, you can still render more than once but you gotta add some boilerplate of your own. Also, in the end Suspense has a very different goal, which is to hold rendering meanwhile you do some async work to not give the user an inpatient loading right away. With Epitath you can write Render Props with the ease of an imperative API, which means you can potentially build your own Suspense API from scratch with it or just use now without needing to change nothing in the internals of your Render Props.
“Can I use it with my current HOCs and Render Props based components?”
YES!
Here’s a demo with React Powerplug
“So, what wizardry have you done to make this work?”
With the flexibility of JS immutable generators.
Wait, you said “immutable generators”? What?
Generators in JS by default keep their own machine state and once you invoke it keeps that state and changes when you call .next
. And that’s a problem for our case. Why? When wrapping one children
callback into another any re-render from a Render Prop would cause the callback to pick up from where it left and call .next
again causing the machine state of the generator to move and creating inconsistence. So we used immutagen to simulate an immutable generator from a mutable one. The whole code is 21 LOC, including line breaks. No magic:
Thus, by leveraging generators power we can even use try-catch and other top level JS structures 🤘. Take a look in these experiments Gabriel Takashi Katakura did with it
Try catch
ErrorBoundary + Async Timer + Normal Render Prop
As you can see you can plug possibly anything. Observables, promises, etc.
“What’s next for it?”
Well, we are using it already at Astrocoders projects when we are not at ReasonReact fantasy land. Though we are delivering this as a proposal for the whole React community to think in a easier and, more importantly, human readable way to write and maintain async code.
We hope that it’ll be a proposal for the whole React community to think in easier and, more importantly, human readable way to write and maintain async code.
Acknowledgements
- Thanks Jamie Kyle for proposing some ideas for the API! 🙌
- We want to point out that after some research after implementing Epitath, we found that someone had built something similar in parallel before. So we want him to have credit on this too! You can see his tweet below
Gabriel Rubens Abreu, Head of Research & Development at Astrocoders.com