Use Prop Getters with Render Props

Kent C. Dodds
InstructorKent C. Dodds
Share this video with your friends

Social Share Links

Send Tweet
Published 7 years ago
Updated 6 years ago

When you're using prop collections, sometimes you can run into trouble with exposing implementation details of your prop collections. In this lesson, we'll see how we can abstract that away by simply creating a function called a prop getter that will compose the user's props with our prop collection.

Instructor: [00:00] here I am using the toggler props collection on the switch and the button. Now let's say that I want to be notified when this button is clicked on. I'm going to add an on click. I'll just have this arrow function that alerts with hi.

[00:17] Then I click on this. I get the alert. Oh, but the toggle state isn't being updated. Hmm, why is that?

[00:24] The problem is that we're spreading the toggler props first. Then we spread on click here. On click is going to override the props that I get from toggler props. If I swap these, then I'm going to get the toggle functionality, but I'm not going to get my alert because now the opposite is happening.

[00:41] I need to actually compose these things together so that both of these functions are called. One thing that I could do is I'll move this on click back down.

[00:49] Then I'll do my thing. I'll say toggler props.on click. That's going to get the behavior that I'm looking for.

[00:59] The problem is that perhaps one day on click gets changed to some other event like on key down or something. Then that's going to break this because toggler props will no longer have on click. You could argue that knowing what on click is for this toggler props could be an implementation detail. All that really matters with the toggler props is that it's applied to the button that's going to be doing the toggling.

[01:23] You don't want users of toggler props to have to understand what the implementation details of toggler props is just to make use of it. That's exactly what we're doing when users have to use toggler props to on click. That's a bit of a leaky abstraction.

[01:35] Another way I could solve this problem is if the toggle component called the render function with some sort of utility or something that allowed me to compose the props that I want to apply to the button, in such a way that it doesn't expose some implementation details. But it also composes the behavior that I want to apply to the button in addition to whatever it needs to do to do its job.

[01:56] To do this, we're going to change from toggler props to get toggler props. This is actually going to be a function that you call. In the switch case, we'll just call it like a regular function with no arguments because I don't need it to do anything special. In the button case, I'm going to call it with an object.

[02:13] This object is going to be all of the props that I want to have applied to the button.

[02:17] Get toggler props will be responsible for making sure it composes together as I would expect it to. We'll say on click and alert hi. Then we'll get rid of this prop.

[02:28] Then we're going to move up here. We'll switch toggler props with get toggler props. I'm going to create a new property called this.get toggler props.

[02:38] Then we'll take this object. We'll move it up to that new method, get toggler props.

[02:44] This function is going to take our props,and it's going to return these new props. A part of this function's job is to make it so that people using it don't have to understand how it works, so I should be able to call the get toggler props function with any of the props that I have. It should compose those things together just perfectly without me having to know what's going on.

[03:04] For example, if I wanted to add an ID prop to this get toggler props invocation, it should apply that ID prop to the button. In this case, it's not going to do anything special with it, but the user of this function should only know that they need to pass all of their props to it, and all of the props that they want to have applied to the button will be applied properly even if it means it's composed like in the case of on click.

[03:27] Let's go ahead and make that happen by adding ...props to spread all of the props that we don't care about across what we're returning here. That way people can add any prop that they like and it'll be forwarded on.

[03:38] But we do want to handle the on click. We don't want that to be overridden when we spread these props. We are going to need to de-structure this to be on click and then the rest of the props. But sometimes people are going to call this without anything, so we'll default this to an empty object.

[03:54] Now let's go ahead and compose the on click prop together. I'll make a new function. Here we'll say this.toggle. We'll make sure we take all the args and forward it along. Now that's functionally equivalent to what we had before.

[04:08] Then we'll call on click with the args, and we should be good to go. Now if I click on this, I'm going to get the alert and I'm going to get the toggle. Perfect.

[04:17] Then I click on the toggle. Uh-oh, something's going on here. The problem is that I'm calling get toggler props with nothing. I default that to an empty object. On click is going to be undefined. There are a couple of ways we can solve this.

[04:31] I'm going to go ahead and say on click and on click. If on click is falsey then this side of the double ampersand will never run. Then we'll just call toggle. Now everything is working.

[04:44] I'm actually going to rewrite this by making a helper function. It's going to be called compose. That's going to equal a function that accepts any number of functions. That's going to return a new function that accepts any number of arguments.

[04:58] Then we'll say for each of the functions, we'll take that function. If that function exists, we'll call it with args. We essentially rewrote this piece just in a more generic way.

[05:12] Now I can say compose on click and this.toggle. It works exactly as it had before.

[05:23] This pattern is called prop getters. It's used in combination with the render prop pattern to make it easier for common-use cases to apply the correct props based off of the state. We use a getter function so we can compose things together without the end user having to understand what exactly is going on under the hood to make the right props apply to the right elements.

[05:44] To do this, we create a function on our toggle component. We accept all the props that are passed to us, defaulting it to an empty object so people can call it without any arguments. Then we return an object of all the props that we want to have applied, composing any of the event handlers that are necessary to make our job work.

[06:01] You probably want to do something similar to this with class name prop if you had something you'd want to combine those together in a sensible way, so that users can apply their own class names as well as any class names that you want to have applied.

Babak Badaei
Babak Badaei
~ 7 years ago

Hey Kent,

The compose function at 5:15 looks more like converge (related to lift)

Compose basically takes the output of the last function it receives and passed up until it reaches the first. The code shown in the video just calls multiple functions with the same paramter at the same time.

Example

const converge = 
  convergeFn => (...args) => x => 
    convergeFn( ...args.map( fn => fn(x) ) )

const parallel = converge( (...args) => args )

Where compose is more like pipe backwards:

const head = arr => arr[ 0 ]
const tail = arr => arr.slice( 1 )
const pipe = ( ...fns ) => input => 
  tail( fns )
  .reduce( (a,b) => b(a), head( fns )( input ) )
const compose = ( ...fns ) => 
  pipe( ...fns.slice().reverse() )
Kent C. Dodds
Kent C. Doddsinstructor
~ 7 years ago

Yep, you're right. Thanks for the feedback!

Markdown supported.
Become a member to join the discussionEnroll Today