4 ms·
Use a Render Prop
- reichardt 9y agoNeat! Didn't know about this concept. Any more examples of this being used somewhere?
- tomgp 9y agoThe technique discussed is a kind of inversion of control, a concept which has a pretty long history (~30 years according to the wikipedia article). I've been writing React for a couple of months now and had settled on this method without knowing it was considered new or in anyway unusual.
- girvo 9y agoUsing ReasonReact with ReasonML sort of forces you down this path due to its strong type system. It’s quite lovely once you get used to it.
- mmgutz 9y agoOther packages have been doing this for a while: react-virtualized, react-motion, react-form ... I wonder why the topic pops up seemingly every week
- tootie 9y agoEven using the term "higher order" that seems like a very OOP solution, while render props is very functional and thus a better idiom for JavaScript.
- mcaruso 9y ago"Higher order" sounds pretty functional to me. Higher order functions and all. Although I guess the term higher order function is hardly even spoken of in real functional languages because they're taken for granted.
- seanmcdirmid 9y agoThe term “higher order” generally indicates complexity and indirection. If you can solve something first order, then that is much more preferable to a higher order (function, logic, object) solution. Higher order functions fall into the same category (you can have them if you want, but you must understand what you are getting into in terms of complexity).
- simplify 9y ago> The term “higher order” generally indicates complexity and indirection. As do all abstractions. > If you can solve something first order, then that is much more preferable to a higher order solution. a.k.a. "To abstract, or not to abstract?". It really depends, of course. But I know for certain I would not want to solve "mapping over an array" without the Array.prototype.map higher-order function.
- seanmcdirmid 9y agoIt really depends. Higher order debugging is more of an art than reality, so if the mapping logic is tricky, I would definitely convert a map into a loop just so I had better access to debugging. It should never be “use map whenever possible.” Also, if you drift out of well known functional origami to more niche uses of function arguments, well, there be the dragons.
- Chyzwar 9y agoHigher order function is a functional pattern. In some OOP languages where functions were not available (Java7), it was not possible to create Higher order function without classes. https://en.wikipedia.org/wiki/Higher-order_function https://en.wikipedia.org/wiki/Higher-order_function In fact, render as props is more an OOP pattern. You pass render functions as props and you use delegation pattern to pass context. https://en.wikipedia.org/wiki/Delegation_pattern https://en.wikipedia.org/wiki/Delegation_pattern ReactTrainig maybe should fix react-router-redux to work with redux time travel.
- hguhghuff 9y agoWhat would the code look like to use this approach for Redux. Could Redux boilerplate be reduced?
- acemarke 9y agoHi, I'm a Redux maintainer. First, could you clarify what _you_ mean by "Redux boilerplate" in this case? The phrase gets thrown around frequently, but it means different things to different people. Just yesterday, I tweeted some thoughts on how you can use as much abstraction as you want with Redux, and linked to examples of reusable action creator/reducer logic [0]. I also wrote a pair of blog posts that discuss the intent behind Redux's design [1], and why common usage patterns exist [2]. It's worth noting that React-Redux was originally written using a "render props"-type approach, but was changed to be a Higher-Order Component before it hit 1.0. In fact, there was a thread a month ago that discussed render props-based reimplementations of `connect` [3], and in that thread I linked to several recent examples of people reinventing that wheel as well as prior discussion of why React-Redux wound up as a HOC. Finally, earlier this year I opened up an issue to discuss ways that we can improve both the "getting started" experience for Redux users, as well as build more powerful abstractions on top of Redux [4]. I'd love more feedback and ideas (and ideally people from the community volunteering to help us make things better). [0] https://twitter.com/acemarke/status/928453589739155456 https://twitter.com/acemarke/status/928453589739155456 [1] http://blog.isquaredsoftware.com/2017/05/idiomatic-redux-tao-of-redux-part-1/ http://blog.isquaredsoftware.com/2017/05/idiomatic-redux-tao... [2] http://blog.isquaredsoftware.com/2017/05/idiomatic-redux-tao-of-redux-part-2/ http://blog.isquaredsoftware.com/2017/05/idiomatic-redux-tao... [3] https://news.ycombinator.com/item?id=15427954 https://news.ycombinator.com/item?id=15427954 [4] https://github.com/reactjs/redux/issues/2295 https://github.com/reactjs/redux/issues/2295
- pspeter3 9y agoAs a note, it is not safe for a component using a render prop to implement shouldComponentUpdate. The problem is that the render prop may close over data that the component is unaware of. The Asana core components used to the use the render prop pattern until we discovered that issue and also realized it made tests harder to write. Now we use React.cloneElement instead.
- doomslice 9y agoWould you mind elaborating on how React.cloneElement works vs this?
- pspeter3 9y ago_On mobile so not a fully detailed response_ I came up with the pattern after discovering the issues with render callbacks and before Higher Order Components were common. You can achieve the same result with a Higher Order Component. The only benefit of this approach over an HOC is that you write a regular Component. To implement the pattern, you make you Component expect a single child for it's `children` prop. Let's assume that the child has a `data` prop which is not set in the parent Component. In render, you return `React.cloneElement(this.props.children, { data: this.state.valueToInject })`. The value of a Higher Order Component is that you can usually define a map to props function.
- acemarke 9y agoYep, I used this approach for a custom form change event buffering component that can wrap around input fields to allow fast updates while debouncing dispatching Redux actions: http://blog.isquaredsoftware.com/2017/01/practical-redux-part-7-forms-editing-reducers/ http://blog.isquaredsoftware.com/2017/01/practical-redux-par... .
- bastawhiz 9y agoIt also means you're passing in a new closure to the render prop on every render. You can move it to a member on your component's class, but that makes your code far hairier. It's also easy to forget to put it on the class, so you end up setting up eslint rules, and next thing you know, you've made everything worse.
- couchand 9y agoThis technique is very useful, but passing the callback as a prop is an ugly way to do it. Much cleaner to pass the callback as children [0]. Then, the final example looks like: <Mouse> {({ x, y }) => ( <h1>The mouse position is ({x}, {y})</h1> )} </Mouse> [0]: https://discuss.reactjs.org/t/children-as-a-function-render-callbacks/626 https://discuss.reactjs.org/t/children-as-a-function-render-...
- andrewingram 9y agoThis is how the technique was first popularised by Cheng Lou in react-motion. But it was generally found that using children made it really inaccessible to people unfamiliar with the pattern. I've literally had good developers not understand them until I switched an example from using children to using a render prop, at which point there's a big light bulb moment. So i'll be sticking with the render prop.
- couchand 9y agoMaybe you could clarify it for your good developers by giving the example with children passed as a prop. Then they'll learn two things!
- andrewingram 9y agoWhat's the point? Given that I prefer the aesthetics of the render prop as well?
- wereHamster 9y agoPeople seem to forget that components (stateless components, stateless functional components or whatever you want to call them) are just functions. One function calls another function, that's function composition. In plain JavaScript we do it all the time. But when React is involved people are suddenly all like: no you can't call that function directly, you have to name it 'Component' with a capital C and use like '<Component />' and to compose two Components you have to use a HOC. It's like they all forget how to JavaScript…
- deleted 9y ago[deleted]
- baddox 9y agoAn HOC is a plain JavaScript function that accepts a React component (and maybe other things) and returns a React component. I don’t think your complaint applies to HOCs, because they are literally an example of your suggestion to just use plain functions.
- arenaninja 9y agoMy biggest issue with HOCs is the hard time you have when things are nested to high hell. A component like withThis(withThat(enrich(withState(withPropsOnChange(withPropsOnChange(withPropsOnChange(withPropsOnChange(....... when this component gives you an issue you begin to wonder what HOCs are really doing for you. Having a render prop is slightly better but even this escape hatch isn't foolproof and you'll still end up needing things like onClickOutside.
- couchand 9y agoI think a lot of HOC libraries were designed assuming decorators would be standardized soon. The connect method from react-redux is definitely in that camp: @connect(mapStateToProps, mapDispatchToProps) class Component extends React.Component { ... } has a certain elegance to it.
- acemarke 9y agoTo some extent, it was - if you look at the earliest versions of Redux, `connect()` and its predecessor forms were indeed being used as a decorator. However, I personally advise against using `connect()` as a decorator, for several reasons: - It's still a Stage 2 proposal. Now, the Class Properties syntax isn't final either (currently Stage 3), which the React team (and I) highly recommend using. However, the Class Properties syntax seems to be much more stable, the behavior it's implementing is a lot simpler, and if by chance it happens to change in the future, it should be relatively easy to code-mod (and the React team has said they would release a code-mod if that happens). Meanwhile, the decorators spec has changed several times (including recently), and the Babel plugins have also had to change behavior and implementation over time. - It obscures the real class definition. The standard advice for testing Redux-connected components is to export the "plain" class separately as a named export, and `export default connect()(MyComponent)`, then import and test the plain version. If you use @connect() as a decorator, the plain version isn't accessible, and testing becomes more difficult. - Going along with that, I've seen many questions about why defaultProps and propTypes don't work right when @connect() is used, and it's because those get applied to the wrapper component, not the plain component, and thus things don't work the way you would want them to. I see no advantages to using connect as a decorator. I encourage people to write their mapState functions separately anyway for clarity and testability (instead of inline as an argument to connect), so it's just a matter of moving the line with `connect` elsewhere in the file and changing the syntax slightly.
- catpolice 9y agoI started today by setting out to write a response of the form "Certainly you couldn't rewrite MY HOC library to use render props, look at how it [etc]" and ended today having rewritten my HOC library to use render props. In the process, I was able to dramatically simplify the API and remove about a third of the overall code. So, uh, thanks.