Higher Order Components

Miguel Camba speaking at Ember London in March, 2017
610Views
 
Great talks, fired to your inbox 👌
No junk, no spam, just great talks. Unsubscribe any time.

About this talk

Since the component keyword and the hash helper were introduced to the framework, a whole new realm of APIs has become possible. With this talk we'll help to spread awareness of the dormant power that developers have, and how and when to use them.


Transcript


Hi, everyone. This is the talk I'm going to give on the next EmberConf, so I'm going to use you a little bit as my personal guinea pigs for this. It's like in a rough primitive state, but most of the ideas are here, even if they are not very well articulated yet, so bear with me. I'm Miguel Camba. I'm a contractor, but I'm working foras well. You can find me on the internet with these handles everywhere and I've been doing Ember for a while and it's something that I enjoy a lot. So our site became involved building I discovered a few patterns that I think are probably very useful for many of you, especially those creating your own component libraries. Either it is for publishing them or for your own internal use. And I want to talk about higher order components and first of all, it's explaining why higher order means on this context. Higher order is a real thing because I thought it was jargon that theparameters use all the time to look smarter than the rest. Yes, they are a real thing. Higher order is a real thing. By the Wikipedia, the definition is, in mathematics or computer science, a higher-order function, it's a function that does at least one of the following. It takes one or more functions as an argument, although this definition is disputed and the one that everybody disagrees is, that a higher-order function is a function that returns another function from within. And that's all to it. That's the very definition. In Java script you had have a higher order functions recently. You have this function called buildAdder and receive an increment and returns another function that takes a number and adds this increment to the number. It's the examples that you will see in every single explanation about this topic everywhere. And this is the example, you create, you call buildAdder, you pass a one and you get a function that you're gonna store it in a variable and then to this function you pass a three and you get four, because the builder was built with an argument of one. Same thing. You build another adder with an argument of four and whatever you pass to that function it becomes whatever plus four. And as you see, the argument you pass here enters on the function as an increment and it's used within this function, so this creates a closure, is the term. You create a closure on this function that keeps a reference to this value on the time this function was created. That's the definition of closure. So when we pass a number we can add it to the closure and we get this kind of private internal state that we can keep that forever. Okay, nice. How this applies to my components because so far, this is function that makes no use to me apart from very specific use case, in certain use case of Java script. The thing is, components are essentially functions. You have a component, sorry, if you have a function and you pass one and two and these functions sums those numbers, you get always three and this is consistent on all the datas on the entry. Most of your components, unless the component loads data itself are functional as well. So you pass clock to the component fa-icon on an icon and you get always this marker, that's function. And the definition of a component is, it's a function that you give it a set of arguments and you get back UX, which is basically DOM plus some events Higher order function are functions that receive some arguments and returns another function. A higher order component is a component that receives arguments and returns another component, but the thing is, component do not return anything. They're not functions, you don't have a return value from a component. And yet, that's right, they don't return, but if we think about it, there is a similar construct in the language in handlebars that, that's something similar-ish and that construction is yield. And yield is a construction that gives back control to the block that invoked the component. We thin that block. I'm using the cursor. You can pass some information from within the component that it becomes available on the block. So this arguments are only available from within this block, so it's like a limited version of the return. It's a return that is only available within this block. And in this example you have a cooker that receives some ingredients and the user and it deals to the outside. They cook the ingredients, user preferences, medium-rare in my case for the hamburger and also give me my favourite drink. On the other side, in the outside I can just bring the meal and the drink and I have these nice emojis from my example. I didn't remember I had these arrows. So, okay we can return something alike. How do we return a component? Returning a component involves the component keyword. The component keyword, it's pretty much using Ember, but it's very powerful and it enables a lot of usages. The first one is the dynamic render. The dynamic render basically allows you to render a component given the name and it can be either a literal string or it can be a value that is bound to a string that contains the name of the component. So it allows dynamic invoking, but the real interesting usage in this case is the next one, is the closure component creator. If to the component keyword you pass an argument, which is the name, and other components, but you don't do it within a context that can be rendered. You don't do it directly within a This is going to create a closure components that is going to be passed somewhere, in this case to gaol. It could even be passed to another component. So if you create a component with this keyword and you can bound some values like a image on the sites and the other component is the user location and you can bind also the show map true. You are creating a component instantiated the component, something alike, but not rendering the component just yet and you're passing this component to your context. In this case, the user card received the user and received this, sorry, received the user and deals this avatar and location components, which are these components we passed a yield and within this block you have access to them and you can use the component keyword on the first usage to render this thing. And this is nice, it's like if you were invoking the user avatar with the user dot picture on the size big on this other component with the show map true in here, but we did all this logic within this component because we don't care from that side. We just know that this thing works. And there is all this, an image, some span text. And the thing is, when we move the component, we can also provide more values that inherits the default ones, or even replace them, depending on the name. So in this case, we decided, yes, well, it's still the default one, so we don't pass it, but we decided in this case, we don't want the default, which is pic, we want the small size. No, I know, I set true by default, but in this case, I don't want a map, so I don't display the map. This pattern is so common in Ember that there's even a shorthand syntax for this. You can use the hash helper, which is a helper at building in Embers things, I think 2.3 or something like that, it's pretty old, where you can create a hash and assign the component to keys, in this case, the keys avatar combined, contains the component user a letter and the key location contains the other component and in the block you gaoled this object. I gave it the name the card. It could be see or could be fool. And you invoke the component dot avatar because it's the name you gaved it. You cannot change this name because now it's the key of an object and you can invoke it this way, or even better, you can invoke it with the dot syntax, which is even nicer. There's of you who made rails will remember the form for pipe F, and then you put F dot input. This is a very similar syntax. And you can even invoke it with a hashtag at the beginning and a slash at the end, so you can have blocks within these components. And this is idea of contester components. This is the definition of how you can use them, but this is very complex, why would they care? Yeah, I need to pass data from the site, ideally for those sites, so I can save just a little bit of bindings. Is this any good? Oh boy, here we go. All of this revolves around an idea I care a great deal about and it's API design. The big font is because this is a subject very close to my heart as a developer. API design is something I care a great deal about. After some time, I realised that I don't care about API just for aesthetic reasons, I cared because they actually make our life easier and with API, it makes you less prone to make mistakes, to make the classical errors or forget something. An API of a component that is good on design, it makes you more productive and these are a few definitions of things I found to be true, generally speaking, when designing with APIs. A good API it starts, the less options you pass to a component, the better, as opposed to, having a component with 10 options. The next one, it's usually one or two values may be okay, depending in the use case, but if you find yourself propagating a lot of it still with bindings, it's very likely that you will run into problems in the future, either because your component not flexible enough, or you find double render issues sometimes with Ember. So if you abuse bindings to communicate with your parent, you're probably aiming for problems in the future. Another one is conventional reconfiguration, that if you component, because of the logic, has to receive let's say, five options, try to not make them mandatory and if you can provide sensitive values for them, do it, so you don't forget to pass on component Another one is, on component hierarchies, it's very common ... to pass all the options to the top level component, because you have no visibility of the components that are inside. That means you have got components who have received the options that then apply to our entire tree, maybe two or three levels beneath this component, because you have a single point of entry for all of them and that means you are passing options to a component that doesn't actually care about those options, it's just receiving those options because it has to be forwarded to something inside. And also, if you find that if you want to tell your component something on the markup and it takes a great deal of pain to make it done, that's a clear symptom of something is not well thought beginning to end. I'm going to present a x-toggle study case because it's the simplest component that could ever think about. It's basically, how will I define it? It's glorified checkbox. It's nothing else. It has a state, true or false and you can toggle it. So the naive approach with this thing is, x-toggle, you pass checked, it's a boolean value and when you click it, you toggle this value. The markup is very simple. I decided to make it tagless, but then I arranged a unique ID, so I can have a label for that ID, and this is the checkbox, so when I click on this thing, this thing toggles it's value and mutates the checked property. Simple. The problem is, we're using this component for, let's say, some preference panel on the user profile and the project manager comes to my table and tell me, I want the user to process this state automatically whenever they change. I don't want them to click a save button to actually save this thing. Okay, okay, I can do that. I'm a developer, that's why you pay me for. And then you realise, oh, if I only have bindings as an interface to this component, that means, whenever they want to do this thing, they need to either, use observers, which we know it's not ideal, or some kind of convoluted computer property with a getter and a setter, then the setter does save to server, perhaps with some delay on the bounds. But we know we don't like this approach, so we decided to use data down, actions up and we pass the checked property, which is one way. We do not mutated this thing from outside. From the inside, we call the onChange action, that does whatever it has to do, in this case, mutated it, probably if the usual thing, but it could be something else and they only change inside, instead of doing mutate the checked value, I call them change faction and the user will do whatever is right in this case. And the API grows very little, but we have the possibility of do whatever we want, so we can call the action save and the action save can do anything we want. We set the check, but then we save to server or we use Ember controller task to do this thing on a restartable way. So we have more control, more fine grain control. Even we could maybe save and try to save and if it fails, we're back to the previous value. We have full control of what's going to happen. But then, the one who comes to my desk is the designer, because they want to use the checkbox in several places, where example, they want to use the green one because it's very compelling, it's very good to accept the terms and conditions of your page and you have a place where you want. Do you want to disable security? Yes, so you want to make it red. And you want some more neutral ones that you want to make it blue, but depending on the case, you want to make big and small, because they are on mobile, you want to make bigger things. They're easy to tap. So you say, okay, I can pass a colour and a size and bound that to some CSS classes. The rest of it is identical. Okay, we don't has the API, but now we have size on small, colour green and it's a shame to have these options that we don't usually care about. So the solution we choose default parameters. We decided that the size by default is going to be small and the color's going to be green, so we don't need to pass this thing every time. We go back to the previous API where we still have more functionality, but API doesn't suffer because of that. And the next problem is that a checkbox sometimes needs a label and not only that, we could add a label in just HTML, but it's not too simple because the label has to be linked to the checkbox, so when you click the label, the checkbox toggles. The names the label has to be labelled for wherever the checkbox ID is, but you don't want to come up with a unique ID that you know that is unique can pass it to both sides, so you decide it is better to make the label part of your component because from this side the component you know what that is. So you go here and if I've receive a label, I'd render a label for the ID, which is the same thing, the same technique I was using for the switch and then I just do checked, label enable notifications and everything seems right. Sorry. There, enable notifications. But they want the labels to have colours, okay, now we need on sizes, so now I need to pass size, a label colour and now I need to have label and label colour as well. Oh, I need two labels, and actually this even worse because the one on the right is always for enabling, so it doesn't toggle, it just enables, and the one on the left, it doesn't toggles as well, it just disables. So you have some labels that sometimes are toggles, but depending on the number, you want to have one on the left, one on the right, one always enable, one always disable. So okay, I can do that. So you have the off label that it calls the action onChange with always false, then you have the same thing you had, then you have the regular label, which is the one that toggles, and then you have the off label, which is the label that always enabled to true. So that's it, you now to have off label, off label colour, on label, on label colour, and you see that, okay, but I also want to have images inside and I want to have a class to this label because I want to be italic and what if I want both labels to be on the left? So okay, I quit. Why is it so hard? That's the point I wanted to make because a checkbox seems to be the simplest thing you can even imagine and then you'd realise that, oh, this can go wrong very fast and this is hard and this is the key idea is that this is hard because we are work despite of following these approaches or the guidelines as much as we could. We could only fulfil these two, but we still have too many options andwe bound a entry for all the options, even if some options only apply to the label or some options only apply to the switch itself. So the problem with all of this is, we're working at the wrong level of abstraction. That's the key, the mantra, It's try to put the information where it belongs. So at this point, we face a logical conundrum on what we are going to do. We can either keep the label and the switch together into a single component because they are logically related, or we can concede defeat, assume that this is too hard to make customizable and we are going to work the logical self every time we use it together, so we're going to pass IDs or maybe we need to mutateto every label to mutate the value, so basically pronounce to have the label within the switch. What should we do? And I say the none or depending on the approach, both. You will see why. I want introduce this or mention this blog post by Dan Abramov, a very nice guy from the React team. Everything he says it becomes either gold and the stock market rises. But again, very nice guy and very, very good article. It has a few React specific things, but generally they supply to Ember or whatever framework you on. It revolves sort of around the idea of two kind of components, presentational components and container components. The presentational components are components with little to no markup that occur about the logic only and they really have, sorry, I'm talking about the one below They are concerned about how things work and they provide data and behaviour to other components beneath, either more presentational components, sorry, more container components or presentational components. Presentational components are just about markup. The styles and appearance. They have very little logic and very little state. Ideally none, but sometimes it has to have some trends in the stating side. And idea of this thing is, we want, in this case, to divide our component, our search, into a label and switch that are logically bound, but from the UI point of view, they are independent unities. How do we do that? We come back to x-toggle and think, okay, we are going to yield a switch and a label contest the components and we decided to nest it for my instructor purposes, but it could be different components and we pass the value checked, the action change and the uid because this information they need in order to work. And we put all the logic we can on the x-toggle, so the x-toggle just has a change action because at the end it's just a checkbox. It receives a value and invokes the action provided by the user, which we don't forget it is still a We invoke the onChange actually with a value. If the value is not provide, it's basically going to be a toggle, so whatever it was before, now it's the opposite. The switch is the simplest thing we have. We struck the markup we wanted, we had before, and we put the presentational defaults in this component because it's where they actually belong. And the toggle label is even simpler, we just yield and we have a component with a tagName of the right thing bound. You have classNames. We bound the size and colour and we bound the uid to the for attribute. And whenever we click, we invoke the action change we received. So in the component we don't need to invoke it in because it's part of the process of markup. So this give us flexibility, composability on the right level of abstraction because in this example, if you want the switch only, with no specific configuration, you'd do x-toggle, you pass the check value on the onChange action and you gaoled the t, toggle, could be t, could be toggle, the name is up to you, and then you render the switch component from this thing. This switch component has the ID and the action responds, so whenever you interact with this thing, you are calling this action here, everything is bound. And then you decide, oh, I want this thing to be red and big, so you pass the colour and the size to the component that actually cares about the appearance of the switch, which is the switch component, not the x-toggle, the x-toggle is only about logic. And if now you decide that you want to have a label, you render a label next to it and you want the label to be on the left, it's just before, instead of after. And then you decide you want a label after and the label before is green, the label after is red, the switch is blue and big. Every option is passed to the component that actually cares about these options. And thanks to this, actually, even without realising, we got something for free. We can change the order of the things, which is something we didn't approach before and if we wanted to have images or icons inside, we could, or we can have class italic and since this it's just a regular component, we are leveraging the framework and we can bind classes, we can bind data, we can bind anything because are leveraging the behaviour of Ember components by default. And all of this is still doesn't make ... your API master of those by definition, because it's, it has to be, but if you decide to only follow the contextual components approach when you have a block, or you don't have a block, you can go and just render the switch component. So the result is that, if you don't care about labels or colours, you still have the same API you had in the beginning. It's not any worse. And yet, you can pass a block and have a much more powerful and much more customizable behaviour. So this decoupling of concerns enables component composition and this is the key of what this thing, or this approach enables for you in component APIs. If you customise presentational components and you render it as the first static stream, you render a component that is bound, you can pass from that site- I want in this example to use not my default switch, I want to create a expression switch for this use case and this switch just need to fulfil this API of calling the change action what it has to be called. Apart from that, it has no knowledge of how other components work or how the labels work. So we decide to create our very complicated- Don't go, you don't want to go here. There is a lot of seizures involved. And we can have my cool switch where you pass. My switch component is my cool switch and then you just render this thing. And that's it, we have the freedom to make. I keep the logic. My designer, it's the best. So my designer can come up with some craziest email on CSS and the only thing I need to code is, I get this thing and I fulfil the API, which is the single change action and I have this thing working and I don't need to throw away the component you had before because before, this basically means, start over. And I wanted to talk about Ember Power Project, because it's this project was built over these ideas. Over time I realised that this power enables a lot of usages and I created a family of reusable components that can be composed together to create new ones. I created a few basic ones, the text measurer, the dropdown, the power select, the calendar and with time actually, me and other people started to build things on top of this and I built Ember Power Select Typeahead, which is a typeahead. The Ember paper-menu component and the paper-autocomplete for Ember Paper. I've built on top of Basic Dropdown and Power Select and there's more examples actually. And I came with this idea of the Ember Power whatever Public API, which as you see, they use this approach of contest for components. The dropdown deals a dropdown with trigger and- This is the content by the way. Trigger and And the power-calendar deals a calendar where they have the nav, which is the thing with the name of the month and arrows to go to the previous month, the next month and days, which is the greet of days of the month. The API that I came with is the one I want to promote, but whatever API you decide, as long as it's consistent with your own components, it's good. It follows this approach. You have the props and the components and the root of the API of the object and then you have an object with the actions, so your actions to communicate are part of the public API and another object with the helpers, in case you want helpers in the component. And with this, you can compose higher order components. In this case, you want to create a datepicker. A datepicker, it's basically a dropdown plus a calendar inside. So if we want to build a datepicker given the calendar, the API we saw before. This one. The only thing we need to do is- This is the API of the dropdown. This is the API of the calendar. As you see they actioned the, the one I mentioned before, to have properties and components on the root level and you have an actions object without actions inside. We just basically need to mash them. That's the only thing it takes. And suddenly you have Ember Power Datepicker, which it's mergeable with where you have the datepicker. You match both APIs and you give it a name. In this case, I gave it the name of datepicker, and you have a trigger and you have a content. Under content you have the nav and you have the days, and the only thing I do inside the template, I render their basic dropdown. I render the power calendar. Both are tagless. They don't have any marker, they have just logic. And then I'd yield merge of the dropdown API, the calendar API, plus some helpers I want to have because they're convenient for me and this assign is a helper I created and is actually available ... on this report. And basically it's a deep merge of hashes. That's the only thing it does. You get a component that behaves as a datepicker by merging two APIs and doingwork, or almostwork. There's a little bit of hand waving here. And there is more still, but the magician never reveals all his tricks at once. So at EmberConf, you will see the Polish version of this. Though we've probably saw more cool examples and see you there.