Code Crimes For Good Component API


When working on component library for a specific company, you want to make it easy as possible for developers to follow the recommended path quickly. Sometimes, that’s not easy. But, when there’s a way, there’s a will! Come see some hacks I have added to our codebase to enable a good API


Hi, I'm sadhat and I look on the design engineering team at GitHub. I'm going to talk to you about some code crime Studio to create good component apis. So before we get the let me ask you what makes a good user interface. So the kind of words that I have got it should be intuitive. It should be accessible and it should be consistent. So if you know how to use one part of the application, you can guess how to use another part, but it's all consistent now. I work on the react side of things. I work on this component library that we used to build other pages on GitHub. And this is a question that I think about which is what makes a component API interface good because that's the thing that your users the developers are consuming and it's kind of the same thing. It should be intuitive to read an author code. It should be accessible by default and it should be consistent. So by consistent, I mean, it should have a small API surface area. If you know how to use one component you can guess how to use another component because the apis consistent So that's the goal at least but it's not always easy. Sometimes it's not it's not in the good happy parts of react and that's where the clients come in. So let's look at a few. I have this component. It's called anchored overlay and it's called answered overlay because it opens an overlay which is anchored to this button so anchored over there and the way this button the way this component works is that you have to manage it state. So you pass open you give it an open and on close function and then you give it a vendor angle. So this is the end of pro you can attend to the element inside in this case and rendering of button and then it passes you some props that you're expected to pass through to the element. So there are things like other crops in this there's some last there's some styles for this angle really is that so kind of a simple API not only going on and then inside it you can put children which get angry so Good component. now the place where we might use this component is the physical request page from GitHub and you see there's a bunch of menus here. You can assign labels you can assign a sign used to a full request. And this looks like a good use case for anchored overlay because there is an overlay and it's anchored to these buttons. now to build this component the way I would go about is I use the anchored overlay. I have to manage it states. I say open I'm gonna do a set State and then just set up and true and on open and set open Falls and on clothes and then rendered ankle. I can use the button component from the components Library pass through the anchor cross and we have this design convention that if A button opens a pop-up or a menu. It shouldn't just look like a normal button. It should have this indication. So we have a trailing icon and characters the default or triangle down icon from the Icon library. But in this case, it could also be this Giver. And I'm going to tweak this button so that it looks like the page. so I like it. It looks pretty it looks pretty good here. And it would work the overlay is anchored with the buttons. Everything's good. Now the thing that I don't like about this is that the the API is not not that so I'm gonna release though. If I have to create this menu and I'm trying to create this into one component and bake some of these decisions in I first of all maybe I'll call it something like action menu because it's a menu of actions and I don't really want the default to be status managed. The component should be smart enough to do this by default that when you click it opens the menu, so I'm going to remove this. And the next thing that I would remove is this render from API again, I want this to be smart enough that it knows what props to pass and then it can pass. On its own so this is kind of hard notes. And feeling I can again it's a design convention that we already know and we want people to follow so it would be nice if it's baked in as the default. So I'm gonna say action menu dot button. This is a button that you use with action menu and it has the triangle the current on the Dayton and finally, I'm going to create action menu overlay to drop the contents because I'm gonna add some props like this is medium. So this looks good to me. Let's jump in the implementation. The implementations are action menu is kind of boring. It's just then does its children? There's nothing special that it does menu button is more interesting. So it's a component which renders the button and you see that will already baking in the trailing light. So this is the default trading icon, but then we also passed on top. So if you wanted to customize the training I can and put this gear like you can Google and the very clear this API of action menu action manual button is is currently but every component in yeah is a function like calls a function and every function in JavaScript is also an object which means you can get away with things like attaching one component to another so you can say action menu not button is actually many about them. So when you render this and you say action the new button it actually calls this function so that And then you have a menu overlay where you use the ankle overlay that componential first and so it still uses anchored overlay under the hood which manages all the state, but when you're creating this menu, you don't have to anybody about all that. So now I'm looking at this component and trying to implement it the state stuff is easy. That's fine. But then I'll just addender anchor which is I need a component to pass down these tops too, right? That's how it works. I need to render this component and the API that we've created is this is the button and this is the overlay so we need some way of actually sending this action menu dot button into the overlay. React doesn't really have a way of communicating between siblings once one child cannot tell its sibling that here is what you need. So. How do we how do we do that? The react we are doing that would be to pull this up to the parent, right? We act like data to be passed from the parent down to the children. And then if you want the opposite way, you have to pass a call back to the child and the child can call that function so That's what we do. We pull anchor up to the parents. Now action menu has a state it defines anchor and then we create a context be pass both anchor inside anchored and black children so that both menu and overly can access this inside the menu and say if answer is not defined. Well, then that's definite set anchor and here is the button that we that we saw and then the props that you pass get passed down to the to be but that works and then we return null because this button doesn't really need to return anything. The anchor is actually by the anchored over this. So inside menu over there we use context we get the ankle from there and we do that right so A little strange but not too wild. This is the way you would abstract data into a parent and then pass it down to all the siblings that needed. And this works. Well, like now if I reload you'd see that the assignees the menu button doesn't render anything because when you overlay and does it, so it lenders both the button and the overview now, the interesting thing is on client side. It's all cool. But if I do this with I didn't dream this is funny thing that happens. Let's see. There's a there's a bit of a large here. Right? And that's that's obviously not good and that's happening because let's say we do a server side render and then pause it. The first vendor on the server. The anchor state was null. And then on the when the anchor is set like when the menu button renders will set anchor and cause a rerender and that will set the cylinder anchor for the overview. So we kind of need two renders though. And if you're doing server side render, then the first time you render you step that HTML to the client and even as the second vendor happens after that after set anchor it doesn't matter because the HTML has already been shipped. And now the on the client the JavaScript bundle would arrive react with hydrate. And then it would call set anchor and that's when it would actually render the anchor. So that's why you see this huge gap and on the server the whole page lenders without the anchor then the bundle is hydration happens and that's when you see it. So again, if it's just line side, it's not that big a deal but the moment we start rendering on the server, especially with slowly works. This could take a while. It is abundance. That's not That's not great. That's not either none of that. So, how can we get this button to render sooner? How can we set the anchor on the server instead of the client? The answer is crime. Sorry, so. What we do here is we say the anchor is null. Let's start with that in the action menu and then react has this top level API of reactor children and it has a bunch of functions. So what you do is you give it props or children and then it lets you go through the child and inspect its problems. One of the properties that child has is childhood type and type is really interesting because it gives you the function that was used to create this component. So in the case of action menu dot button, it would give you the function that with you which is this menu button, which means you can do things like this you can say child or type and just use the reference to compare it if Charlotte type is menu button. That means you have action then And then you can actually just say set the anchors I'm saying anchor was not a mapping through if it's menu button. I'm just saying I'm going to start it and if it's manual overlay, then let's clone that element and pass it an additional drop again. All of these are top level apis and and passing the render anchor Pro and creating that on the Fly and passing it to menu overly. So this is super interesting because all of this has happened even before action menu has returned anything. So now when even on slot 3G when I reload You'd say that everything is coming from the server. This is already done. And this is an interesting interesting pattern and the constraint of pair of course is that you have to use When you button on menu over there, you can't use a third component and we throw in error. If you try say it's actually then you does not identify what your past. Please use one of these or refer to the documentation and then add a you know, a nice comments on my co-workers because I'm not a monster. So let me ask you is this a hat? Absolutely. Hell yeah, but is this a good time? probably so a component that You know, let me put it this way the goal of a good component just like good UI design is to abstract away annoying words, especially work that is not code to the goals of the user or the developer in this case. So sometimes clients are okay, you know and better way to put that might be that hacks that made the code easier to read an author. Helping the developers do a good job quicker within reasonable constraints of the system a kind of okay. So the reasonable constraints here are that sticky using action menu, you have to use action menu dot button and action then you don't overlay which works really well because those are also components from the same component library and be using all of this to build guitar, maybe if you're an open source Library like material or chakra, you might not be able to impose those constraints people can bring their own button, but it works well for us though moving on good take a deep breaths. Okay more things. So we have this component called a novelist a navigation list. And the way this component works is that you have another list and you can render now, let's start item inside it. So we use this on the settings page where you have a bunch of components a bunch of setting pages. And then the I this item as a list you can have I can you can have a shift. So for example, when I click this public profile and this is the selected item and the way we show this selected State on this item is by using the audio current prop, so, in a list of in a navigation list It's it's good practice. It's thick that makes the page accessible the screen reader where you say all your current and then if this is the current page that is selected then you say are your current stage? Otherwise, you said not. So we try to reuse this audio property to show the styles for the for the selected State as a way to nudge people or promote people to use accessible properties. And of course, there's a bunch of these the settings is like really long GitHub is Big so we have a bunch of them. It only makes sense to start grouping them in some day. So created this component because now list or no it really similar. It takes the title. It takes an icon and then it takes again now this dot items you can have now understood items without any Chrome or you can group them inside this and you get a you get a nested navigation list. So what's interesting about this again is the way we show the audio current we show the current selected. Item navigation item is by reading audio current from this list. So when you reload this page use the selected item would be highlighted you see too but what's more interesting is if it's part of a growth then this group would be open. So if I try to open public profile, all the groups are closed, but if I reload on one of like billing and plans, which is part of access the group would be open by default which makes sense that you want to know where in the thing you are. So the group basically has to know if one of its item is the audio current value. So how does this work now? Let's just kind of boring. It's navigation with a list inside it. And now item is also kind of boring. It's the list element inside because you know, the parent is a UL. So this has to be an ally and then we render a link component from the design from the component site really. So all of the fun things are in that so now group of course is also list element because it's lenders inside a URL and this thing is a button because you click it and you you can change state. It has leading I can read the children's next to it. And now we need a way to basically identify if this is open or closed. So we set State we say the manage the state the state can be toggled by the button and then we can even do this carrot as a trading I could like add it up or carried down right like this one. So all this is pretty cool when you're when you're interacting with it. But what is the default value of this the kind of need to know if this now group as an item inside it which has all your current so that we can set that as default open true now. A reminder that this is what we're looking for, but we were looking we kind of need to access this value up here on the growth. So one of the options could be just ask the user right after developer who's using this component to set the default value and that obviously works. But if you think about what the developer experience here would be what kind of code they would have to author it would probably be something like this there. You have to maintain a list of all of these and then say if it includes any of them, then this should be open and they're already doing part of this information here and this field is a built redundant this obviously wouldn't work on SSR. So you probably would have to access like router here and it just feels like a lot of work that isn't core to what they're trying to do, which also means that it can be missed and then you get bad user experience. So a smart component would just absorb the sound so How do we set this value automatically one way of course would be to do crimes to pick the components out of the children and we've done something like this before then we can say default open as false and then we might through all of the children and if any one of them has audio current on them, then default open should be true, right that makes sense. And that's very easy. But remember I said that that's that make code is a lot of within reasonable constraints of the system are okay. Now when we started testing our is this a valid constraint can we can navigation list? Sorry can the not list true assume that it will have this item and be able to pick Aria current is where we ran into problem. So very often people would use a different router like they can use next router or react about remix out. And then what they would end up with is navigation. This might have a custom component inside it like and even That the audio current might not even be on now. Let's start item. It might be on something like next late. So we kind of lose that they can't just go into the component and read the prop stickers. That wouldn't be the case here. So those are not reasonable constraints in the system. We can't even rely on callbacks because as you can see here now, let's start item doesn't even know if it's selected because this is a property of next link now, they could change a few things we could again go back to default open, but it really nice. It's just component could do it on its own. So if you can't assume children, how how can we set the default value? The answer of course is crime. So what we do here is the creator left and then the attach it on the root element of the group and after it has mounted the imperative which say contain another current query selector for audio according and this is a fun thing because very selectors happening on rendered Dom. So it doesn't really care about how your components of structure is it a direct child is a deeply nested. None of that really matters Victoria. Well, in this case, it's already rendered and it's somewhere inside the growth. So we do a query selector and we do set of it's true and of course even you know, it totally works so And what's what's interesting about all of this is like is it an antibodyne? It kind of feels right when we imperators API calls with query selector inside a react component which has a declarative design. So it's kind of a 90 bargain, but this is a chord crime. Familiar. I think the goal of a good component is abstract away. All this annoying word and to figure out if a group has an item selected. I'm open it feels like this is not cool to what the settings page is, right? You want to focus more on what settings are you changing? What are the forms for the Music Experience? This feels like an extra perk so I think it shouldn't be part of it. So okay this crime feels okay. Okay one for the road because I shot one and there's something this this is not perfect yet. If it's on client side, then it's fine. You know, it doesn't really matter. But if they're using SSR then it kind of leads to this that use layout effect would only happen on the client side not on the server but half an up the mount so you end up with something like this, but it's closed on the server and then on the opens in the climb, which kind of I mean, don't get me wrong this kind of looks cool. I like that. This is like a tiny animation. So that works but if that's on slower 3G then well, it shouldn't worked. Yep, this is good. See if it's on so it's easy it would open after Edge really long while right like it doesn't know yet the bundle arrives and only then it hydrates runs use the artifact and opens and this is a really long time. You might already be looking at some other setting and then suddenly opens and distracts you so that's not really good. The answer the thing I've been give you is that it's of this decline. Right? And what we want is we want this to be we want the default value to be set tuner right? We don't necessarily need this to happen on the server. We just have we just wanted to be sooner. and that's all the hint I'll give you and I'll leave you with this last part which is actually go hacks that make the code easier to read an author helping developers do a good job within reasonable constrainable systems are okay. So sometimes crimes. Okay. All right, that's me. That's me and better. I'll see you there. I said it's always great to hang out with you. Unfortunately, you're not here. But I feel that we did this last year as well. Join in virtually. It is good to see you again my friend. How are you doing? And regularly, how are you? I'm doing good. All right, let's jump into the questions. We have a question, which ask is it a good idea to implement a workaround that depends on the Frameworks internal apis and what if reacts decides to stop supporting that in the future. That's a good question. I'd say it internally is definitely not. But if it's a top level API, right? Yeah. then you can get away with it. I'm going to be very honest. Most of these are hacks, right but sometimes that's get the job done. And I feel at this point with the big react team is committed to supporting backward compatibility. The kind of arm interesting them. They can't really break any of these top level apis. You know, what's it next time. Someone asks me a tough question about is a good idea to make that decision. I'm gonna grab that side. I'm gonna quit you on it as well. One thing actually those people I always love your presentation style the way you show us life code and show us today's demons. Someone's actually asked about that. So you use what are you using to control the speed test on examples? Because when we are building our own platforms you want to sort of do these tests in different environments? What do you use? Right. So usually what I do is I use the Chrome Network Tab and that has multiple emulation for the talk. I'm not doing any of that that was a simulation that I did basically like easy. This is how much speed I'm allowed and then I would load on my action chord. No problem. Like we said hacks are good if you can get away with them. All right, but about hacks. Do you think hacks? Like these can impact the readability of the code in the long run, especially when you start finding things in places maybe didn't expect to see them. Oh definitely. So I think the big caveat I put here inside. I wouldn't recommend these hats if you only need them one Solitude and you know, if it's an application line the way I justify my hugs are. Some of these somebody has to do them, right if they act doesn't have a top level solution for this you have to put that code somewhere. So it's I would rather put it in the library that gets reused and write a bunch of tests for it Dan put it closest to the user that I'm probably not writing as many tests. So I think it affects the readability and also the maintenance like we write a bunch of tests for these climbs because you are playing with fire. It's you miss something out you could cause a bunch of things to break so It affects it. But I feel like you have to find where to put the complexity because it has to go somewhere the put it where you can maintain it to the most confidence. One thing also this is kind of my own question. I'm throwing in here. One thing I love is every time I get to watch one of your talks. I see something new I've learned about Design Systems and things that you are very getting from seeing how developers are interacting with the design systems that you work on. How do you go get in that feedback and seeing the behaviors that they are building with and then trying to kind of implement maybe good practices from a design system perspective. Mm-hmm. That's a good question. I think would be end up doing a lot because they use GitHub to build GitHub and it's better. We just end up looking at patterns and there are patterns that could repeated abundance. And in those pattern we kind of bring them. In to the design system liners, they're doing figma code that I've been doing storybook stories for pictures and then we kind of start from the end. This is what we'd like our users. The developers are very good help to have so what kind of abstractions do we need to build to support that? So I guess the first stage is inventory where you just look at all the things that exist and all the things that can be simplified or can be made into a better pattern. And then you start from there. Thank you so much it. I know someone popped in a question right at the end. Unfortunately, we don't have time right now. But if you head over to the speakers Q&A room or if you're online and you pay in online go to the speakers Q&A in Discord, you can ask it and he can answer your question. Let's give it a round of applause one more time.
28 min
21 Oct, 2022

Check out more articles and videos

We constantly think of articles and videos that might spark Git people interest / skill us up or help building a stellar career

Workshops on related topic