About this talk

An examination of best practices in designing performant React applications as well as highlighting some of the more complex issues that can come back and bite you. Will also cover various libraries out there that help you get the job done.

Transcript

- Hi, my name's Adam Reynolds. I've been pretty much doing React from a professional point of view, I've been a contractor, for the last year, year and a half. Prior to that it was Angular and a lot of Node, prior to that, you may have heard of something called ColdFusion and JavaScript and all that type of stuff. Have you heard of ColdFusion, no? PHP, something like that, yeah? Don't know, some time ago? Actually, Lovehoney still use it, so that's where I did a lot of work there. And then prior to that I was doing stuff with Ada and C and mission planning systems and air traffic control, and in fact, if you fly out of this country, you fly through my code. Which should worry you, based on the quality of this presentation. So what I wanted to talk about is, because I do, I suppose I have lots of languages, a lot of times where getting your head around concepts is really quite hard, and once they click, that's when things get easier. So when you talk about React, a lot of the problems I find, I think, with React, is just getting your head around the idea of React, and in particular, how you go about designing in React and that is something, or actually, in most of these frameworks now that are using this sort of virtual dom, this kind of node tree approach, and that, I think, is something that I think a lot of people probably miss out on. So if you actually look at the way that Facebook looks at stuff, they'll break the UI into a component hierarchy. You can, I'm not even gonna talk my way through this, you can pretty much see what they're doing there. One of the things they're doing here is identifying high-level components within which you have components with single responsibility, and the key bit about single responsibility is it makes testing so much easier. So if you're testing, start with the idea that a component only does one thing well, yeah. You can then test that very well, and then get your unit testing done. And that is a key part, no matter what you're doing, whatever development you're doing, testing is one of the key aspects that you should try and avoid. So what you then do, usually, within a React style environment, is that you will then create your static version of it using either some crap data or whatever. So here is a little bit of stuff where I've just copied and pasted it, it's very exciting, but actually, it's very boring, because we've got some more slides to go through. So we'll then go on to the next step, which is to try and work out, from a state point of view, or when you're looking at your components, what type of data it should hold. So when you talk about components within the React concept, you have properties which are passed into a component and you also then have local state within the component itself. And the idea behind that is that usually you should, I'm just gonna get a keyword here. Mutate data. You should be trying to maintain the state. So if your properties are coming in and then you're converting that data and storing it locally, you've got a problem with what you're putting into your component. And this is one of these things that, when you're looking at where you think you should be putting data, and this is where it gets quite complicated. State, whether or not you put it within the component, whether or not it's held within a parent component, or whether or not you actually go above and actually effectively have a container which contains all of this data information that's passing it down to the child components within it are very key parts of thinking. You can explore this yourselves, I'm just gonna run through this quite quickly. This is again, for what goes into this, it's just trying to work out at what level in your component hierarchy states should be held. So for example, you'll find, if you've got a form element, an input field, you may be holding the state value of the current value of that field within that component. But what you might also be doing, and this can get quite complicated, is if you start holding state at the wrong level but you're feeding it into multiple components that you're finding that you're updating unnecessary other components as part of that. So if you had, for example, a field where you wanted somebody to type something in, and as that state was changing, but you also held within that state a list of products, existing products, every single time somebody pressed that button and you had another component that rendered that, or that component rendered those lists of products, you'd find that those would get updated. And your performance would drop massively. So thinking about where you store data is absolutely key to any React design. And then, of course, as I mentioned, was this idea of where, how you broke up set data, how you communicated that down. So a component shouldn't normally, if a component is presenting data where it's getting the data from, so for example, say the name field is you're storing it on a higher parent level, that parent level should also be providing you with the function that then updates the state at that level. You, as an individual, that component below it should be only doing its single responsibility which is displaying that particular value. I'm gonna go quite quickly, and that's pretty much it, thank you very much. But it's not, because we've got another 27 slides. What we're looking at here is that, and when it says saving with the view libraries and all the others, is this idea of going away from what most people know of as model view controllers style, the design pattern, and moving to something called flux. And flux is this idea that you have this store, which is reflecting a view, and you have actions which are dispatched via the view itself, so you're clicking on something, which updates the store, which then reflects what's happening inside the view. So when you look at components, you're not doing as you did with Angular One, and I've had a long experience with this, is that you're not setting the value inside the component there and expecting it to then be reflected back on the state there. You're actually dispatching an action which does that work for you. And so then we move on to thinking about component itself and how these things live. 'cause a component itself goes through a number of steps. One of them is the mounting phase. So you have the properties which are passed in and the constructor of the actual component. You have a question of, that the component is then declared that it will be mounting. Then you have the thing where it's going to display the component, and then you have componentDidMount. componentDidMount is only something that happens on the browser, and this is actually quite key when you start looking at React and you're trying to do server-side rendering is you can actually get into the situation where you go, well, I'm about to mount the component, I'd better go get some data for it. That's a nice idea, but you have to make sure that you have it in the componentDidMount section purely because of the fact the way the data is you're retrieving it usually by an HTTP request or some other way and that is actually something that's happening inside of a single page app at the front end. We then have component lifecycle from the point of view updating data. You have two options. There's one which is you've updated the state or you've received new properties. Now, if you update the state, this obviously, the first one won't actually trigger, yeah? The next one is absolutely critical. The shouldComponentUpdate is one of those functions which allows you to be extremely intelligent about whether or not you want this component to re-render itself. The reason why that is so critical is because when you start using things like third-party libraries, I don't know if anybody's used Leaflet. Leaflet is a mapping solution, it puts a map on, you can drag markers around. So in theory if your properties that you're passing into a component which says please render this map and you start dragging a marker around and then you suddenly see your marker bounce back, and the reason why is that you may have had an update that said, oh, this marker is at this position. And so we rendered it, and then when you finally release it, it goes. When you, well, with the marker. So the idea behind is is that I could tell that particular component to stop updating itself while the third-party library did some stuff and then release that block on the component updating once I'd updated all of the local state as well as dispatched an action to say this particular marker position is updating. And then the other one's quite important. What you'll notice here is we have this componentDidUpdate and componentWillUpdate. These are quite critical, primarily when you're debugging. I find those are quite useful, because what you're doing is you can actually see how long particular components are taking to render, and if you're looking at performance, there are performance libraries out there which help you get around that. And then of course the final one, which is the unmounting. Unmounting can be quite useful, because what it's basically saying is that that component is going to be removed from the dom, and what you find is that you may have services which are, for example, you may have a regular update of a service where it's fetching data, refreshing. When that component is removed, you wanna clear down those particular timeouts, because otherwise, you'll be sitting there wondering why you're getting prompts through for something that no longer exists. And that could mess up the rest of your application. It can also be quite useful. One of the things that you need to be very aware of is when you start getting into things like redux and these kind of global state repositories is the idea that you want to be able to cancel requests as well. So I will talk about a library that I use called Axios, I mention it, you'll see a slide in about 10 slides about it, but the idea behind it is you have this concept of cancelling a request and so you have a cancel token which you then generate, pass into as part of an HTTP request, and if the context of the application changes, you may go, say, to a different user or different product, you've been retrieving data for that particular product, you can cancel that whole process from actually continuing on and then not polluting your state, your store, with that extra data that is no longer needed. And although this sounds quite odd, what you'll find is, I've seen, I've actually done this code myself and it's really bad, is you'll basically see, oh, I've got some new properties through, it's a new product, what's the product ID versus the one I've got in my endpoint, params.productID equal to props.productID, no, it's not, okay, I really don't want to see this. And it's that sort of, when you start writing things like that, you know you've got a problem which you're not overcoming. You're patching it, you're basically putting a plaster over the top of it. There are some other ones which are quite interesting. There's setState. Now, setState is, from a point of view of React, it isn't something that happens immediately. It will batch up state updates, and one of the key parts is that if you actually do need to have something happen after the state has been updated, you should use callback, I'll actually present a callback function deliberately for doing that. The updater itself, most people probably just use it with an object and just say here you are, here's some values. It can actually be a little bit more complicated. You can do some interesting stuff with it. So you can actually start looking at the previous state, current properties, and all this type of stuff and start manipulating some of the data that's coming in. And of course forceUpdate is one of those ones. It's primarily, when I've used it primarily is with something called pure components. Pure components are components, but they don't, they don't really update as much. They try and, once they've got their data in, the properties are in, they will do shallow compares, they won't actually look deeply into the properties. And they're also one of the key bits is they usually will not, actually, usually, they don't ask child components to update themselves. So what you'll find is that, and so if you do have a pure component, make sure its children are also pure components as well. So container presentation pattern. I mean, this is kind of the standard thing that people have got to when it comes to components. A component either is a container or it's a presentation component. You can read that stuff, I won't. What it's trying to do is that a presentation component usually either consists of dom elements plus other components and also other containers. It's very exciting. Data usually isn't mutated, it's basic displayed, and that's it. It doesn't try and do anything with the prompts that are coming in. When you start looking at container components, container components are more concerned with how the whole application or what particular area of an application sits together. And so you have got to look at it and say you usually find that container component doesn't provide any dom elements. There's no H1s, whatever, spans, anything else like that. It's dealing purely with data transformation state, maintaining the state and passing it down to child components. It's one of the key parts of when you start splitting stuff off and it helps from a point of view testing is you're trying to get your presentation components testable easily, and giving them single responsibilities is really important. The container to container component is usually an absolute pain in the ass. And then we move on to something called higher order components. Higher order components are simply components that decorate other components with extra functionality. It sounds boring or simple, but it can be quite complicated. Can be as simple as somebody saying I'm gonna add a style, or wrap this particular component in a div and provide a style on top of it. It can be as simple as that. It can also be as complicated as putting a data wrapper around a list component, which then makes sure that the data, and then defining the endpoint differently. What it allows you to do is the key, and this is one of these things that's really important. It allows for cross-cutting your concerns. So what you do here is you can have a comment list as a component and then provide different subscription endpoints, so you have a consistent list which has no idea where the data has come from but the higher-order component knows how to configure it and provide that data through. One of the key bits behind higher-order components is that they don't try and mutate the component itself. So you notice here is we're doing this, in this bad way, is we're actually taking the prototype of the input component and actually hijacking it. The problem you can get into is you have no idea if the next higher-order component is doing exactly the same thing, and you can end up overwriting it, so don't do this. So one of the big clues about it is you usually end up returning the actual input component itself, and that means you've done this wrong. What you're far better off doing is use composition, where you're basically wrapping the actual component with the extra functionality around it and delivering that to the front and back again. Right. Right, so. There are, from a point of view of higher-order components, they can start off quite simple. For example, you can add a router to a navbar, you can start adding in configuration into it, or you can go a bit crazy, which is the connect from Redux actually returns your higher-order component which you then apply again to the comment itself. What they allow, so from a point of view of simplicity, the idea of higher-order components, get your heads around it, understand it, understand it can be quite powerful. I mean, the fact that you're wrapping Redux, a route is a higher-order component solution, is actually quite key. But it can also do simple things like add styles. And then the other thing that is from a testing point of view and a debug point of view, this is actually quite important, is make sure you're actually allocating the display name correctly, because you can sit there going, well, my component's saying this, and actually the bug is actually being generated inside the higher-order component. And that sort of thing can be quite painful to try and actually identify. And then we go on to pure components. I've already discussed them, I'm not really going to go much further than just talk about them gently, which is the fact that they are basically a way of creating fixed content within your application, which you know doesn't get updated quite often. If you do have a pure component that you want up to date, you can use Force Update on it. So you can actually, if you know that you're going to once in a while have a pure component which mostly sits there doing nothing but once in a while you do want to do a deeper check, you can look at the shouldComponentUpdate and actually put in there a force update for a specific scenario. Right, next thing. I'm gonna now just generally talk about libraries that I've used, abused, and learnt about, and gone, oh, I wish I'd known about this at least three months ago, 'cause it all evolves every three months. So obviously, react-router, people have, I'm guessing, are familiar with react-router or most router solutions. It's basically, I think it's version four. Version four ruined everything about version three and everybody was very angry about it. Even version two and version, no, actually, they were happy with version three, but they weren't happy with version four because it came out way too quickly after three. But you do need a routering system should you be looking at more complex applications, and something like react-router is, as far as I've found, to be the best one out there. Obviously Redux. When you start looking, you can use, there's another one called MobX, each of them are slightly different. Redux is quite complicated. One of the problems you find with React applications is the setup processes can be an absolute pain in the ass. And Redux doesn't help the whole setup itself, it actually adds more headaches onto it, particularly when you start using selectors and then all that stuff. Anyway, the whole problem with it is is that without it, you'll find down the line you're doing a lot of painful stuff. Nowadays if somebody says to me we need another action that does this, blah blah blah, I go to one file, I go into the Redux section of a directory, I update the store a bit for that particular aspect, add in the actions and all that type of stuff, and suddenly those are then exposed to the component that's needed and that connects through the cores and do whatever it needs to do. And it's quite a simple way. But getting your head around that and trying to understand Redux sometimes is almost worth giving up and just accepting it works, yeah. Yeah, sorry, once you've been there, you go, oh really? Okay, well, it does work, okay, fine, carry on. Recompose. Recompose is phenomenal. I would recommend it. If you know Lodash, I like Lodash, yeah. Recompose is one of those libraries that just gives you extra toolsets. It is a very good, for things, one of the things that you'll find, for example, higher-order components, yeah, it allows you to chain them together, so basically put a list of them together and it will just build up the component itself and compose the whole component and you're not having to do this add one and then the next one, it basically does it for you. It's a beautiful piece of code. Well, a beautiful library. I strongly recommend it, it will save you time. Axios, the reason why I like Axios is because of cancelable requests. Axios is also a very good HTTP client library. The reason you absolutely, once you get into Redux, when you start having actions and the whole flux pattern, things like cancelable requests are really important, because one of the things you'll find is that end users don't play fair. They all click a button and then they'll immediately click another button, you're going, but you're still waiting for that data. Please stop. And that's not the way that works. So you have to find ways around, or manage, the PEBCAK problem. And of course Immutable. I don't know if anybody around here is familiar with Immutable. One of the key components behind Immutable is that if you make a change to an object, you get a new object back. And it makes, from a point of view of comparisons, you can just say, are these two objects exactly the same, yes or no, yeah, carry on. You can't go in, actually, you can, but it's really painful to do. You can start trying to set values, but what it's basically saying is that within this object, if I wanna set this value, I can set this value, but you will give me a new object back and I will then carry on using that new object. What it means, though, is that particularly between, where React is very sensitive to this idea of immutable objects, you know, changing an object itself is a very indicative to components that you then have to update what they're displaying. Okay, react-addons, the perf one is really good. If anybody hasn't looked into this, it basically allows you to put in performance information between the componentWillUpdate and the componentDidUpdate. You don't usually want it in your dev, you're never gonna save it to, don't do instal minus one and save. 'cause that is not something that you want out there. In fact, to be honest, you wanna keep that one quite secret, you don't want anybody else to know about it, because it allows you to analyse components very carefully and look at how fast or slow they're working and where your problems are arising, and then remove that code just before you identify really cleverly in a meeting it was that thing that did it. The other one that I like particularly is create-react-app. And one of the reasons why is, for anybody who's ever gone to a hackathon and tried to create a React app, you'll get the configuration done by the end of the first day and by the second day you might actually have something to display, yeah? So create-react-app is a fantastic solution. It just does, it's easy. It does all the webpack configuration, everything for it, it's even got the testing involved, so that's good. And yeah, just use it. If you're gonna start with React and go hey, cool, what can I do? It takes four, five minutes to have a good React solution running. The only problem I find with it is I really wish it would add in the ability, I don't know if it has yet, it's been at least a week since I looked at it, whether or not they have put react-router in and whether or not they're doing things like Redux. It would be lovely to have that. I know they've got a typescript script, so you can actually have React typescript scripts, I think that's the way you say it, as part of the installation. And so you can actually be using typescript with it. But the problem I find is that at the moment it's not recognising some of the core other things you bring into to create a modern SPA. A couple of other things. Testing React apps. If you've looked into React, you'll know that most people are now using Jest. One of the things you'll find, though, is that what React has done is create a virtual dom. And this is what a virtual dom sort of looks like. So when you write in your tests, you don't actually need to do any kind of rendering as part of it, you can just sit there and say, does this element exist inside this? So when you start looking at your testing, sometimes you can just test the virtual dom rather than actually render this whole solution then go try and select the particular element inside your JavaScript, the actual dom itself, and then get into quite a painful selecting process. This can save you a lot of time and pain because the point is that your dom, your virtual dom, is going to be what is rendered at the front end. Yeah, sounds simple, I suppose. Progressive web apps is next week. I'm not gonna talk about them, but you should be doing them. They are one of the reasons, yeah. They are good, you can create a React app now, deploys itself as a progressive web app. So start looking into them, understand how they work. It's going to be one of the ways that people are delivering solutions now, because they want that ability, they want, in essence, what you're trying to do is say, once I've got a set of data, I don't need to connect to the internet to actually continue to operate. So the other one that's also coming up quite a lot is this idea of CSS versus inline styles. People are kind of pushing this idea of inline styles and how performant they are, but the reality is that within an application environment when you got probably eight people working, there's always the designer, and the designer isn't gonna know JavaScript. And neither should that person expect to know JavaScript, but they should be really good at CSS or SAS or Less or whatever you want to choose, and these people are quite vital, because they stop me having a headache. And it's people like that that will then deliver a better solution than you could think you could do. There are times when, within a component, you may wanna update the style, because it's a very local update. For example, you may have, I don't know, you want to underline it red, but you still get into a situation down the line where a director says no, sorry, I really don't like red, I want pink. And now I gotta go through the whole of the JavaScript library, or component set, remove my funky little red higher order component, oh, well, hopefully you've written it as a higher-order component. If you haven't, then it gets you even more screwed. So the other one, oh, that's got small. Working with third party libraries. The only thing I would say with a third party library is usually what you find is that React is a declarative thing. It's basically saying, I'd like a beer please, and it will get me one. There we are. But if you look at a lot of libraries, they're not written in a declarative fashion. They're basically implicit. So basically what you'll find is a lot of the third party libraries, for example, things like Leaflet, as I mentioned earlier, will turn your reference to a map object which has been decorated with all of the Leaflet functions. And you, then, have ability to access within your React component all of those functions specifically. It can be quite painful, particularly when you start having the map object getting overwritten by React, and usually what I find is I immediately try and find a way of disabling React from interacting until I really need it to, using a shouldComponentUpdate. Yeah, the end.