React JS developers have lots of options for building their applications, and those options are not easy to work through. State management, concurrency, networking, and testing, these all have elements of complexity and a wide range of available tools. It's hard to know what to do. If you take a look at any specific area of JavaScript application development, you can find highly variable options.
Kent Dodds is a JavaScript teacher who focuses on React, JavaScript, and testing. In today's episode, Kent provides best practices for building JavaScript applications specifically in React, and he provides a great deal of advice on testing, which is unsurprising considering he owns testing JavaScript.com. Kent is an excellent speaker, and he's taught thousands of people about JavaScript, so it was a pleasure to have him on the show. Kent is also speaking at Reactathon, which is a San Francisco JavaScript conference taking place March 30th and 31st in San Francisco.
And this is the last show in a week of interviews with different speakers from Reactathon. If you hear something you like, then you might want to hear more at Reactathon. You can also hear more podcast episodes about React by listening to the Reactathon podcast, which is available at reactathon.com slash podcast. Kent Dodds, welcome to Software Engineering Daily.
Thank you so much. I'm super happy to be here. You've been part of the JavaScript ecosystem since before React won out as the most popular front-end framework. Did you take any lessons away from the framework churn on what causes an open source library to succeed over other rivaling technologies?
Ooh, that's a very interesting question. So just for a little bit of background, I joined the JavaScript ecosystem and actually the software developer ecosystem. Around the time that Backbone was the thing that most people were using and AngularJS started to become a thing. So I think I've been doing JavaScript for about a year before AngularJS started to become the new hotness.
And then it was another year and a half or so after that that React started to become pretty interesting to me. And so in experiencing that firsthand, I think that there were a couple of things that kind of helped React win out as it has today. One of those would be that AngularJS has at least at the time and even still today, there's a huge amount of complexity within that library because it supports so many things, whereas React is so much smaller and simpler. It's a smaller slice of the front-end world.
And so conceptually, it's a fair amount simpler. And on top of that, it also embraces JavaScript as the language for the platform upon which you're building. And I know that Angular people listening right now are like, no, no, you're using JavaScript all the time, like TypeScript and whatever. But what I mean by that is the template DSL that you have to learn to use many of the other frameworks, you don't have to learn that to use React effectively.
And so in fact, you are better off with React if you know JavaScript well. And so that's one thing that really appealed to me personally when I was making the switch from Angular to React. When I was switching off of Angular, I was leaving behind a huge amount of knowledge that I'd accumulated over the years because that was just not transferable at all. And that helped, I think, in general, and just like as a general principle, React being a simpler solution for building UIs, I think the way that the component model came at just the right time, AngularJS was still all about directives and controllers and stuff like that where React embraced this component model, making it really composable.
And the really nice thing about that component model in general is you can have this really complex component that could be all inside of this black box. And it really could be a tight-sealed black box that the outside doesn't need to know anything about. And that just makes it easy for you to say, you know, this part of the code is super complicated. It could probably be simpler.
It could probably be made better. But the complexities within that black box do not leak outside of it. And so I can still use this thing. It's still useful to my application, but it's not going to impact the simplicity of the rest of my application.
So I think, yeah, a combination of that. And then also we can't discount the issues at the time when AngularJS was moving over to Angular 2. And that was basically a big rewrite for the framework. And React itself is actually undergone a rewrite.
Ember has undergone a rewrite. But the API that's been exposed was consistent for those. Whereas with AngularJS to Angular 2, it was just so different. In every single way, there was no automated tool you could use to upgrade an entire code base.
And so that, I think, contributed a big time. And for me personally, when I was working with AngularJS and starting looking into Angular 2, and I was actually advising the Angular team on some things with how you do forms, because I was a big maintainer of a form library with AngularJS. And I was giving talks at conferences like this is what you have to look forward to. And I just saw that as like, this is basically, they could have just renamed this thing, because it is so different from the original.
And it was going to be a huge burden. Like my company wasn't interested in rewriting to Angular 2. And I know many others are still on AngularJS. And so with the combination of all of those things, when it came down to it, people were like, well, I can't stay on AngularJS.
I need to move to a different framework to stay on the latest of things. And so the transition to Angular will be just as complicated or maybe even more complicated than the transition to React. So I guess now I just evaluate them on a level playing field. And I decide that React fits my mental model better than what Angular 2 is going to.
And also the fact that Angular 2 just took such a long time to come to production. A lot of people had already moved over from AngularJS to Angular or to React before Angular 2 was released. I did. A lot of reasons that React came out on top.
And some of those are subjective. But I think what is objective is React now is definitely the most popular framework for the JavaScript ecosystem. And not- Do you feel like you're all in on React at this point? Or do you try to keep up with view or spelt or whatever else?
Yeah. I'm definitely all in on React personally and professionally. But I do try to keep tabs on what's going on in view and in spelt and whatever else is coming up next. I think any software engineer who doesn't want to get left behind with Flash or something would find that it's wise to keep abreast of what's going on in the overall community for sure.
React has been around for, I guess, six or seven years at this point, really popular, probably for five years. What are the most notable changes that have come to React since it came out? Yeah. So as you said, React was released six years ago.
And there have been some changes to the framework. I think maybe one of the most notable was the change from React.create class to classes. In that change, just how you create these components. That was pretty significant.
But the cool thing about that was the API itself was so similar that they were able to write an automated tool that would just automatically update all of your code, not necessarily from create class API to classes, but to a separate package that you could add and still maintain support for your old components and then write all of your new components as classes, which is what the Facebook does. And actually, that's one thing that I really like about React as much as I'm not a huge fan of Facebook at all, either as a company or even a platform for my own personal use. The fact that React is being built to support the needs of a platform like Facebook is really awesome because it means that if the React team says, hey, we need to make this big change, if you want to move from React.create class to official classes because that's where the web is going, then they have to consider the 50,000 or 100,000 other components that are built within Facebook before they can make a change like that. And so they either have to write an automated tool to make that migration really painless.
And actually, I believe at Facebook, if you're going to make a breaking change, you have to be the one responsible for updating the code that you're going to break. And so it behooves them to make that as easy as possible. And so that, anyway, that was one change that happened. It was really easy to upgrade that.
And then another change that is pretty significant is the hooks API that was released about a year ago. And that allowed us to do pretty much everything that you can do with React component using just regular functions. And it really kind of changed the way that you think about components and lifecycles in a way that makes it harder for bugs to pop up. At least, like, maybe it takes a little bit more work when you're developing to make sure you get it right.
But you end up shipping for your bugs to production, which I think is a good thing. And then we're we've got another change coming up pretty soon with how asynchronous works and react. And that'll be a pretty significant change. But all of these changes are totally have a really nice migration path or they're totally backward compatible.
Facebook still has components that are years and years old written in the very early days of React. They're still being run on production on the latest version of React today. And I don't know of any other framework that that can say that. And I just really appreciate that about React.
React has this reason change as you mentioned called suspense, which improves the experience for asynchronous loading. Explain how suspense works. How much time you have? No, just kidding.
Yeah, so suspense. It's an interesting, interesting idea around how allowing the users of React to Q react in on when it's okay to do rendering or when you'd rather wait to render certain things. So it's kind of complicated and a little hard to do over audio. But there's a really great talk that Danny Brumoff gave about two years ago at JS Iceland that people should take a look at for sure because it explains the use cases in a way that I haven't seen anybody else explain it any better since then.
The basic idea here or the problem that we're trying to solve is that the web is inherently asynchronous. Not only like waiting for buttons to be clicked or the user to do something, but also the network. When the app gets loaded, we need to go make a request, the user clicks on this button, and so now we got to go request more data. And we can't show the user a loading screen, but we can actually show them anything useful until that stuff comes back.
But then you have all these problems where you have a flash of loading state because maybe we make the request, we show the loading spinner, and then, oh, 100 milliseconds later, 200 milliseconds later, that request comes back. And so we render the final version. Well, that's what we call a flash of loading state, and that makes your app feel slower than it actually is. And so in that scenario, it would be better to not respond to the user at all.
They cook on it, you just wait 100 milliseconds, and oh, the stuff is back, we'll just render it out. And so then the app feels slower. It may have taken a 100 milliseconds to actually respond, but it actually feels faster because by the time it responded, it was actually the real data. And there are a lot of complexities like that.
It's not just for that, but it's in general managing a synchrony from a user experience perspective as well as the code in the developer's perspective. Is it tricky business? And so what suspense allows you to do is allows you to create these boundaries around areas of your React component tree to say here, from this suspense component on down, if any of these components says that it's not ready, we call that suspending. So if any of these components suspends, then I don't want you to do anything with the rest of this tree, just wait for that component to say that it's ready to go.
And there's an API for that, you know, pretty reasonable. And then when it's ready to go, then we continue to render that part of the component tree. And if it takes too long for that to happen, let's say it's not 100 milliseconds, but it's like two seconds to get that data, then you can provide a fallback to say, hey, if this does take a little bit too long, then render this instead. Maybe it's like a skeleton UI or something like that, try to make it look at least it's partially loaded or something or show some indication to the user that work is happening.
And then once that suspending component has received its data, then it informs React, hey, I'm ready to render, react to that part of the component tree, and then the user sees the final result. So today's like current way that we do this is by managing all of that state inside of our component. We have some loading state, we have error state and all of these different states that we manage. And thanks to hooks like this, it's actually pretty simple to abstract.
But at the same time, it's harder to coordinate these types of loading experiences. So you don't just wind up with 12 loading spinners when the user lands on the page, which also will make the user feel like the app is slower than it really is. So suspense, the whole idea around it is just making a synchrony, a better experience for the user and a better experience for the developer who is trying to marshal all of these to coordinate well together. Can you describe it in more context how a hook interacts with the suspense mode?
Yeah, sure. So one thing that I'll mention before I jump into that is that suspense is actually a supported feature today, and it's been around for over a year, and that supports lazy loading of code. So you have this React.lazy API where you can use a dynamic import. And so if the user goes to a login screen, they just get the code for the login screen, and then when they go to the settings page, then we dynamically load that code later.
And so it helps with initial page load performance. So suspense has been around to support that use case for over a year. And the suspense mode that we're talking about is suspense for data fetching in combination with what's called concurrent mode in React, and using those two together is what gets you all these extra superpowers. So how suspense interacts with hooks is actually they're pretty unrelated to each other.
And so you can use suspense with cost components just fine. There's really nothing about the two of them. The only relation that they have is that you can have a custom hook that is responsible for suspending a component. And the way that that works is this is actually, I get the similar response to when people see JSX for the first time, they're like, wait, that feels very wrong.
But this is how it works today, and it's probably going to work that way when it's actually stable. But inside of your component, when before you return your JSX while you're in that render phase, you create a promise or you take a promise that's been created already for making this fetch request. That's preferable. You don't want to create promises in your render, but you reference promises that have been created.
And you say, oh, that promise hasn't actually resolved yet. So I'm going to throw that promise. So it's like throwing an error. You say throw that promise.
And that throw will be caught by React. And it will say, oh, you threw me a promise. So I'm going to wait until this promise resolves before I try to re-render this component again. And so how that interacts with hooks is that you can put that promise throwing inside of a hook.
And that abstraction, or that hook abstraction can hide away the implementation detail of actually throwing a promise because nobody actually wants to do that in their components. And nobody will. That will all happen within abstractions, especially like data management libraries or fetching libraries and stuff. And so as far as the user is concerned, or the developer is concerned, they're looking at this code and they don't see anything in there that indicates that this is happening asynchronously, which actually makes that code way easier to work with.
And then the user experience is improved because they're leveraging all the research that the React team has done to create this concurrent mode plus suspense API. That's kind of, they don't really necessarily interact, but you can use them together to do some cool things like that. Would you distinguish between what suspense is versus what concurrent mode is? Sure.
So concurrent mode is something that the React team has been researching for an extremely long time. And only in the last, I think it was like two years ago when React 16 was released, that's when concurrent mode, or the code necessary for concurrent mode was added to React. That was a complete rewrite of the React framework to support concurrent mode. And it was called React fiber, that rewrite, but now it's just React.
And the idea behind it is performance, the big secret to improving performance is less code. So you either load less code or you run less code. But that's how you make any software faster is less code. It may not be less characters, you know, that's not what I'm talking about.
It's just how much time is the computer taking to execute your code? And so because JavaScript is single threaded, the time that the browser is taking to execute your JavaScript code is time that it can't have to update the browser or what the user is looking at as the user interacts with the app. So let's say that you have an interaction that takes 500 milliseconds because it's just running so much JavaScript to make that interaction happen. During that 500 milliseconds, the user is unable to interact with the application at all.
That is just a really unfortunate scenario, especially for users on mobile, they experience this a lot more. And so what the React team discovered is, hey, like sometimes you can absolutely reduce how much code you're running. And like there are optimizations you can make as the developer who's creating the application. But sometimes you just really have to run all of that code.
Like there's a reason that code exists. But if you can run some of it now and then some of it later and then the rest of it at another time, you just split up that work, then you can let the browser take a break from the JavaScript code and then let the user interact with the app. And when the user's done, then we jump back in with that work we were previously working on. And so that's what fiber was all about.
That's what concurrent mode is all about is it says, hey, I'm going to, like React says, I'm going to run all of this code. And every now and then I'm going to just check, like, am I ready? Or can I continue to run code or do I need to yield to the browser and just pause what I'm doing? And eventually, like sometimes it's like, oh, I finished all my work.
I don't need to yield anything. But sometimes it needs to yield back to the browser because something it's doing is really heavy. And so I'll yield to the browser, let the user interact with the app, whatever, and then go back into the JavaScript code. Now this happens very, very quickly.
But what the end result is for the user is an app that feels so much more snappy. And there are certain interactions that the user can do that can jump ahead of the things that React is doing as well. So they built out this scheduler, and that's actually an MPM package. And they want to build this into the browser so that other frameworks can take advantage of this.
And they're actually working with standards committees to make that happen. But it allows you to schedule work to say, hey, this is really high priority. Even if the user is interacting with this, we really need to make this JavaScript happen. Or you know, at this network request, it came back.
It's not super imperative that we jump ahead of the user typing into this field. So I'll go ahead and let them type. When they're done, then I'm going to continue to do my work. So it's just this really fascinating scheduling capability that React is built into itself.
And that's what concurrent mode is all about. It's enabling developers to utilize that scheduling capability that's now built into React. And today, with the way that React is shipped, you can use concurrent mode with the experimental build of React, or you can just use synchronous mode. I don't know if that's what they're referring to it as, but normal mode.
And both of these are supported today. And eventually, I think concurrent mode is going to be the only thing. You touched on an element of web development that I want to get your perspective on. So JavaScript is fundamentally single threaded.
And the browser is often switching between the user's application code and painting the elements that are being loaded on the browser. And I'd just like to get your perspective for how React and modern JavaScript simulate the experience of working in a multi-threaded environment versus the realities of JavaScript being single threaded. Yeah. Well, so with concurrent mode, because React is a framework, it's calling into your code as the user of React.
So you create these components and you hand those over to React, and then React will call your functions. And then when your functions are done, being called, then React will continue. And so every single one of your function components React is going to be calling into. And then that function component will return the React elements that it needs to render.
And because React is in control of all of this, then it's able to do these checks to say, okay, based on how long I've been running right now, I have X amount of milliseconds left before I need to yield to the browser. And so I'm going to go ahead and call this next piece of user code. And then when that user code comes back, then it does another check. And so, okay, now I need to yield to the browser because I don't have time to call another piece of user code.
I believe, and I'm not on the React team, and I've tried to read the source code and I understand some of it. But there may be some nuances here that I'm missing. But the basic idea is because React is in control of calling into your functions, it's able to do that kind of management of calling into or yielding to the browser when it's time for the browser to do some work. You are a full-time instructor, and I want to ask you some about that a little bit more in the future.
But you spend a lot of time interacting with people that are learning JavaScript and learning React. And I think one of the things that confuses people is React state management. So this is often the world almost always done around Redux. Can you explain some of the common misunderstandings that people have around React state management?
Yeah, that's a great question. I think that maybe two years ago we could say that React state application state management was done with Redux, or mostly. But in the last few years, Redux has fallen out of favor quite a bit, and I don't think that it's the de facto standard anymore. There are a few reasons for this.
But first I'd like to talk about why Redux took off so much. And I'll preface all of this by saying I used Redux only one time in a production application just enough for me to realize that I didn't like Redux at all. And I not Redux the technology, but I guess what Redux encourages developers to do, which I think is not good. We'll get into that.
Redux came at a time that we call the Flex Wars, where Facebook came out with a specification that kind of, it was basically a talk in a document that said, this is how we manage state in our applications to avoid certain inconsistent debug. So a ton of different libraries came out. I even built one actually for managing state within this kind of architecture. Eventually Dan Abramoff was scheduled to give a talk at a conference, and he wanted to do this time travel debugging thing.
And so he built a little Flex implementation that would allow him to do this kind of based on the architecture. And the talk went super well, and everybody asked him how they can use this project that he used to make that happen. And so he'd open sourced it, and people started using it. That's when Redux became super popular.
But it didn't just become popular because of a talk, it became popular because it solved the problem. The problem is, if we go back to AngularJS, we have dependency injection, and that's how we get different services and data around to all of our components, it's just the single namespace that's available everywhere in the application. With React, you don't really have that. Instead, with React, you have this explicit passing of props from one component to another.
And that actually works out really nicely because with dependency injection, you have all of this global namespace that's implicit, and you can register things later, and it just can be a bit of a headache. It's kind of like a global namespace on window. But with React, you're doing this explicit prof passing, which makes it a lot easier to track, okay, where's the thing coming from? Oh, it's coming from the parent component.
The problem is that when you have a tree that's of sufficient size, of a regular production application, you're going to have some trouble with passing these props down six different layers of components, where you're passing it from component A through component B, C, D, and E, where component E is the only thing that actually needs that prop, and so B, B, and C, and D, they don't care anything about it, they're just forwarding it along. So then you start moving components around, and you have to make sure you're passing the right prop. So I don't know if I need that prop. Maybe I do, so I'll just keep doing it, but you don't, and so now you have this extra prop, you're doing all this extra work for, no reason.
We call this prop drilling, and it is a bit of a problem, even though it's nice that you can track exactly where things are coming from. It's problematic because it just takes a lot of work to maintain. So and then eventually you do things like spreading all of the props across these components, and that can lead to some problems as well. So prop drilling can be a bit of a pain.
And so what Redux did was the integration with React, is it used this API called context that has been supported in React for a very long time, but during that time, it had this big warning on it that said, this API will change, do not use this. So that freaked everybody out there like, well, I'm not going to use that thing. But Redux did, and so did React Router, and lots of these pop-up like theming libraries and CSS and JS libraries, I'll use this API that was eventually going to change, and they just said, well, I'll deal with it when it changes, but right now it solves this problem of getting data from component A all the way down to component E without having to pass it between these other props in the hierarchy. So this context API is what Redux used to be able to get your data from the top of the tree down to the bottom of the tree in a way that was really easy, and it used these higher order components to make that really easy to do.
And so the problem that Redux solves wasn't necessarily unidirectional data flow and time to travel debugging and stuff, it was actually just making it easier for developers to get their data to the components they needed without having to go through all these layers of prop drilling. So that's why Redux became popular, because it was the first real reasonably simple way to do that without using APIs that the React team told you not to use. So in recent years, the context API got some love from the React team, and they made it official, and they made it actually quite nice to use. So now you can actually start solving that same original problem without Redux, and in the process you sidestep all of the issues that you get with Redux.
So let me talk about some of those really quick. Redux in itself is not necessarily a bad thing. The point is that you want to keep your state as close as possible to where it's being used, because that makes it easier to move components around, it makes it easier to delete the state that is no longer in use. It makes it easier because you don't have to open up like 30 files to find the flow of data in your application.
And so in general, Redux made it simpler for people because they found that if I needed to share data between two sibling components, I'd have to keep on moving it up and up the tree until eventually I'm at the very top of the tree. And so just putting it into Redux makes it a little bit easier since I don't have to prop drill all over the place. So the problem is that when people are so used to using Redux to manage state in that way, they eventually start putting everything into Redux. And so now you have your form error state in Redux.
You have every keystroke of the user in these form inputs going into state. You have the ismodal open state like happening in Redux as well. You have to start namespacing this stuff because it's in this giant Redux store. And so this becomes a problem because now when it's, when all you have to do is update a component that has a checkbox that says whether or not it should show a certain message or something.
Now you have to open up like three or four different files that are who knows where to manage that simple interaction. And not only that, when you open those files and start making changes, the changes that you make could have completely unknown impacts on the rest of the code base. You have to, all of a sudden, instead of the next component model that we left from React where it's just this black box and it doesn't impact anywhere else in my app, now literally every single component has wires connecting it to every other component in the application. And so that's what I don't like about Redux.
It's not the technology in itself. Like if you're being responsible about what you put into the Redux store and the state that you put in there actually should be global and does care about the other state that's in there, then yes, that's reasonable. But most literally every Redux implementation that I've seen in the wild just puts everything into Redux and it has these problems that I've talked about. So anyway, as far as like state in general and like how you manage state today, I actually have a lot of thoughts on this and I'll stop talking here in a second.
But I think one of the big problems that we have just when we're talking about state in React or any front end system is that we kind of combine all state, all data that can change over time, which we just call state into one category when actually there are many different categories of state in our applications. The stuff that comes from the server, that's actually not state, that's a server cache. And it's something that you can't reliably expect to always be consistent. And so we cache it onto the client so we can display that.
But you actually have no way of really knowing if that's the state of the server. So it's not state, it's actually a cache. But we combine that with the application state like our modal is open or the menu is open or we've got the user check to this check box. That's UI state and that's different from our server cache.
And so when we combine it all together, it makes them both more complicated. But if we can logically separate those two, then all of a sudden our application state management becomes a lot simpler. And this is something that I've been thinking about a lot recently and I'm still kind of working through my thoughts on it. But those are a bunch of words for you to think on.
To take it to a related to an episode that we did recently. So I talked to somebody from the Slack front end team and Slack uses a pattern that I hadn't seen before. Maybe this is common in front end. I don't do as much coverage of the front end world as I should.
But they use this pattern where if you want to snapshot your Slack state so that you can, like if I go sit down at my desktop, I work in Slack and then I go and go sit down at another desktop and work in Slack in order to maintain the same state. They snapshot the Redux store and put it in a CDN and that way I can access the state by just, you know, they download it from the CDN and they push it out to my Slack application. So I just use it to, I guess, explore with you. Is it a frequent pattern where the Redux store or whatever state management store you're using is saved to the cloud and then you, you know, when you reboot the application somewhere else, you load that state?
That's an excellent question. I don't think that I would, and that's actually a really cool technique. I don't think that I've seen that in wide use, but it makes total sense and it's not something you'd have to use Redux to be able to accomplish. But I can see how Redux could make that a little easier.
But I have definitely seen that sort of technique used for error monitoring and reporting. So if there, if user experiences an error, let's snapshot our state, send it to our monitoring service so that we can see what state the user was in. Yeah, I think that, and actually because Redux makes that a little bit easier, then that's a pretty good use case for Redux. I would just caution anybody who wants to use Redux to be really thoughtful about what state they put into their Redux store and maybe explore a couple of other alternatives as well.
What are the most common performance issues that you see in React applications? That is a good question. Questions about performance so often as an instructor, pretty much every workshop that I give, regardless of the level of experience of the attendees, has something, some question about performance. And most of the time it's kind of this armchair performance where you're sitting back and you look at something and you say, I think that's probably slow.
And when you actually measure it and you're like, yeah, you're right, it can only do 4 million operations per second. So if you need to do more than that, then maybe look at something else. Yeah, so most of the performance problems that I see, and again, I mentioned this earlier, but the number one solution to performance problems is less code. So you load less code, you run less code.
And I think that the lowest hanging fruit for most people is loading less code. I don't think people are code splitting as much as they should. And it's so simple to do, especially since we have React.lazy and suspense. So you can code split on any component in a way that's pretty simple.
You don't want to go overboard, but you definitely can be methodical and surgical about where you're doing code splitting and save yourself quite a few bytes that you sent to the user. You certainly don't need your entire application loaded on the login screen. Like that, nobody should be doing that. And in fact, I would argue that you may not even need React on the login screen, but that's a subject for another time.
So that's one half of the thing. The other half is running less code. And I think the number one thing that people try to optimize for when they're talking about running less code is re-renders of components. So whenever there's a state change in React application, React takes the component where that change occurred and it re-renders that component, which will trigger a cascading effect of all other children of that component will be re-rendered.
Now I actually have a blog post about this. I think it's titled One Simple Trick to speed up your React apps or something like that. But if your component receives its children as a prop rather than just rendering its new React elements itself, then React can actually pre-optimize to say, oh, these children couldn't possibly have changed, I'm not going to bother re-rendering those. And so restructuring the way that you write your components and actually writing things in this way is I would say more idiomatic, even though not as many people do it this way.
It just makes more sense to write things this way rather than having the component render the JSX or the React elements that it's rendering, accepting those as a prop to the component and saying, this is where those should go, whatever they are. But that's one optimization that you can make that it's just a little bit of a restructure. Another thing that, as part of this whole re-render problem, a lot of people just see re-renders as bad. Like if it didn't need to re-render, then that was a bad thing.
And so what they'll do is let's take a scenario that is actually, I see quite frequently. So let's say that we have a component that takes 50 milliseconds to render. That would be a very long render, but we'll just use that as the number. If it takes 50 milliseconds to re-render, that's very slow.
And so we want to, and then we notice that it's actually re-rendering unnecessarily two times. So it renders necessarily once, and then un- well, let's just say unnecessarily one extra time. So now it's taking 100 milliseconds to run when it really should only take 50. So what people will do is they'll use optimizations like React.mo to say, if none of my props changed, then I should not re-render.
So either the props changed or the context it was consuming or a couple other reasons that it would re-render. React.mo just says, if none of the props changed, then I should not re-render. OK, so we fixed that problem and we can move on with our day. Now this thing is not re-rendering unnecessarily.
It's rendering only one time and it's taking 50 milliseconds. Let's look at developer B who looks at the problem and says, wow, it's re-rendering twice, that's taking 100 milliseconds. That's a really long time. I wonder if I could speed that up.
And so they optimize the component instead. And they make that component now render in 5 milliseconds. OK, so now it's only taking 10 milliseconds to render twice. And we're like, I've got bigger fish to fry.
I'm just going to move on. And so the difference at the end of the day is eventually your component absolutely has to render. Something changed, it does need to render. So with developer A, their component is going to take 50 milliseconds when it renders because it was necessary.
Whereas with developer B, it's going to take 5 milliseconds because it was necessary. And so you're better off there. And then also with developer A, to avoid that unnecessary re-render, they had to wrap it in this React.mo thing, which adds complexity to the code. Because not only do you have to add that complexity, which isn't very much, but if you need to add a custom prop checker, I forget what it's called, a custom function to determine whether it should update.
If you do that, then you do add quite a bit of complexity. So you have to check everywhere that's using that component and ensure that the props that it's passing are going to be consistent between renders. And so that has this spidering effect where maybe that component got those props from somewhere else. And so you have to keep on going and memoize all these values just because you wanted to make sure that this one component didn't re-render unnecessarily.
Where if you just went with developer B's approach and said, let's not care about how often it re-renders and just speed it up so it doesn't take so long, then you don't have that spidering effect and your app is faster to boot. And eventually it absolutely doesn't need to re-render. So you just want that to be faster anyway. So anyway, that's a long way to answer your question.
But this is something I see a lot is people just instantly reach for how do I reduce the number of times this thing is rendering? When they, before you should ask yourself that question, you should ask, how can I make it so that this thing renders faster? Now there are sometimes when it is rendering all the times it needs to or it's going as fast as it possibly can, but it's still re-rendering unnecessarily. And because I'm rendering 700 of these things, I need to make sure it's only rendering when necessary.
There are those times, and that's when you reach for these optimizations. That's why those optimizations exist. But reaching them as the first solution often has the spidering effect that goes throughout your whole application, making it more complex. So that's a problem that I see quite often.
You've spent a lot of time studying how to properly test JavaScript applications, giving your condensed thesis on how to test JavaScript applications. Yeah, so the philosophy, I have done a lot of work on testing with JavaScript, and the philosophy that I've come up with over the years is the more your test resembles the way your software is used, the more confidence they can give you. What that means is if you are writing tests that, well, let's take a step back. Your software, if we're talking about React components or backend or anything, your software probably has two users.
It has the end user that's actually interacting with your application, and it has the developer user that's using your API or rendering your React component or calling your function, whatever it is. So you have those two users. Those are the two use cases your software needs to continue to support, or like is useless, right? When you start writing tests that don't resemble the way that your software is used by those two users, what you're doing is you're inviting a new user that your software needs to support.
I call this the test user, or the third user. And the test user, the reason that we write tests is to have confidence that our software is going to continue to work. I know some people like to write tests for their workflows, and that's awesome. Feel free to do that.
But the reason that we commit tests to source control and run them on CI is to make sure that our software continues to work as it was designed, that it continues to support those two users. So once you start writing tests, that user software differently than those two users, then you've created this third user where you have another point of failure. So if you change something that breaks the use case of the test user, literally nobody in the world cares except for that test user. And so what that means is your test user exists for themselves, and they're not paying you any money.
And you're not making the world any better by supporting the use cases of that test user. And so there's no reason to bother supporting the use cases of that test user. Now, sometimes we have to make trade-offs. Testing is just up and down trade-offs where you have to mock the credit card, you know, a charging company because you don't have enough of a credit limit or something.
You're like, I don't want to charge my credit card every time I check out. So you do have to mock some things. You have to poke holes in reality sometimes. You make that trade-off, but you acknowledge the fact that that is not using your software in the way that it really should be used.
And you counteract that by maybe having a smoke test against some other service or like Stripe, I know, has their test service, and so you can maybe interact with something like that. And just have one test that makes sure that at least this thing works, but then the rest of your test can operate on a mock. But that's my whole, like, condensed philosophy around testing is the closer you can align your test to the way your software is used by the two users you actually care about, the more confidence it can give you and without giving you confidence to test is useless. And that's why I created TestingChobScript.com, so I could teach people how to do this effectively with the tools that I think do this the most effectively.
That's why I created the testing library for React and Angular and Vue and Ember. Well, Ember is technically not, there's not an implementation there, but you can make one. Like Puppeteer, TestCafay, Cypress, I have this testing library so that people can do a better job of just naturally running tests the way that their software is used. Writing tests has always bored me to tears.
And I know that sometimes you have to write tests. Perhaps most times you have to write tests. But can you tell me when can I avoid it? When can I comfortably avoid defensively avoid writing any tests at all?
Yeah, that's a great question. And you're not alone. So many people do not like writing tests. And it's very reasonable to not like it because it really can be often difficult to identify how this is helping the bottom line or helping you make the world a better place, whatever it is you're trying to do.
And so yeah, places where you can avoid writing tests are things where you don't care if it breaks. So you don't care if you get a call at 2am in the morning about this thing breaking or even better you wouldn't get a call at 2am in the morning because nobody cares. So a good example of this, I was just working on an open source library I have today that very few people use and I have no tests on it. And like lots of people see me as the JavaScript testing guy.
And so like, I was live streaming and I had a bunch of people watching and I knew that some of them were going to be like, what the, you don't have tests, what's wrong with you. And so I just preemptively mentioned like nobody uses this thing or very few people do. And the people who do can just use the previous version until I get this fixed if I break anything and it's not going to like bring down their CI or anything like that. So I don't really care too much.
And so if you have some software that you're writing and you don't really care too much about the impact of a breakage and this could happen even within an application. It's actually really important, a really important distinction to make that like if there's an area of software that it doesn't matter that much if it breaks, then that's not your place to be focusing your time and effort. And really testing is no different from any software. The reason that we test or the reason we write software is so that we can avoid doing things manually.
And what we're all about is like, how do I make this thing that I do manually happen faster? How do I crunch these numbers faster? How do I analyze this data faster? Whatever it is, that's what software is for.
Testor exactly the same. How do I make sure that my changes do not break when I shift this to production? That's what testor for. And if it's not something that you care too much about, then there's no reason to automate it.
So like we've got this one page that like six of our users use, they only use it like twice a month. I'm not going to write tests for that. And we don't change that code very much anyway. And when it does break, they don't mind reaching out to us, whatever it is, there are so many other ways that I can make the world a better place in my day that I'm not going to waste my time writing tests for that.
And instead, I'm going to build this feature that will improve the experience for my users. And so like you have to weigh this and just like think about, okay, I've got X number of things to do. Some of those are tests. And we have this one page that we have so many people using it because it's our checkout flow.
And when they click checkout, if it doesn't work, that is so bad for my business. I'll lose millions of dollars for that checkout button. So yes, I'm absolutely writing tests for that. Like I'm not getting around it because that's how I can make the world a better place the best is by making sure I never break that page.
And then once you've got those tests in place, then you think, okay, what's the next thing to do to make the world a better place? Well, there's this feature that people have been asking you about. And I haven't had time to build it. I think that will make the world a better place better than writing tests for the settings page.
And so that's where like I don't see writing tests as any different from writing software features. It's really okay. I've got this priority of things to do. What's the next thing on my list that will help me make the world a better place better than anything else?
And sometimes that's test, sometimes that's feature, sometimes that's bugs, and sometimes that's going home and playing with your kids, whatever it is. All right. Last question. It's 2020.
What does JavaScript fatigue mean today? Yeah, JavaScript fatigue. I lived through it. Back in the back one days before it really ramped up and then Angular just came out and then React and Ember were going on and Angular 2 was coming and then we had the Flex Wars and they're like 30 different libraries.
So, and then all the tools like Webpack versus browser 5 and then wait, Parcel is what is this? And now, so we've got all these different tools coming out and testing tools as well. I think that people are still innovating a lot and you could still build an application pretty well using the tools of five years ago, but you can build an application faster and better using the tools of today. You just have to acknowledge the fact that the tools of today are still being iterated on and improved.
So, yeah, what does JavaScript fatigue mean today? I think that we experience it less and the tools have really solidified pretty well. People have a pretty good understanding of what different tools are good for, what they're not good for. And we have even better than that, we have a better roadmap for new people to follow where we say, hey, if you want to get into React, then you're going to use Create React app or you can use Yatsby or Next.js and those tools themselves have a really good ecosystem within themselves of lots of resources and they're just really user friendly.
You don't have to make as many decisions. And I think that's actually where JS fatigue comes in and play most is where people have to make a lot of decisions about things. And view is actually very good at this and Ember as well, where they don't let you make decisions, they just made them all for you and you just follow it. And 90% of the time you're going to be just following those decisions that they've made for you.
So React especially, I think, is prone to decision fatigue or JS fatigue because the React team is all about making the core library really good and then they leave it to the community to fight over what's best. And so we do still have a little bit of that in the React community. But I think we're slowly coming to a consensus on what the best tool for 90% of the jobs is or what a good tool for 90% of the jobs is. And I think we're going to continue to go in that direction.
Now we do still have cool new things that are coming out that people should continue to analyze. Like just two years ago, I released React testing library. Svelte has been out for a while, but it was just like a year ago that Svelte 3 came out and it's considerably different and much better. So you still want to keep up with what's going on.
But I think that we're slowly, like we're maturing as a community. We haven't been around for as long as many of the other communities. And there's just so much innovation that's happening in our ecosystem because we're maturing in a day and age with GitHub and npm and with these tools that make this kind of open collaboration and maybe even premature collaboration possible. So I think that's kind of why and understanding why maybe explain a little bit why that fatigue is slowly tapering off and I think eventually will mostly go away.
Ken, thanks for coming on the show. It's been great talking. Yeah, thank you so much.