Single Responsibility Principle in React (Design Patterns)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
In this video, we're talking about design  patterns in React once again. And this time,   I want to focus on the single responsibility  principle, which is one of the foundational   concepts of the SOLID principles. The single  responsibility principle, or SRP for short,   is not just some fancy term that we throw  around without really meaning anything. It's   actually something that will help turn your React  components from a terrifying "super component"   that literally tries to do everything,  to a component that is a lot smaller,   a lot easier to manage, and also reusable. By the  end of this video, you'll know everything that you   need to know about the single responsibility  principle in React. You'll also understand   why it's important to use it to really build  scalable and manageable React codebases. And   we're also going to look at an example of  a component that doesn't follow the single   responsibility principle and how we can refactor  it to make sure that it conforms. So get ready,   get excited, you are going to learn a  lot in this video. Alright, let's begin. What is the single responsibility principle?  And how does it apply to React components? The   single responsibility principle states that a  component should have one and only one reason   to change. In other words, it should only do one  thing; it should only be responsible for a single   thing. In React, if you build your components,  following this principle, you actually get a lot   of benefits. The first benefit that you would  get is you have reduced complexity. By dividing   your components into smaller, more focused  parts, it makes it easier to understand and   manage your code. And this is really important.  Whenever you have a React codebase, you want to   make sure that it's easier to understand and to  manage so that you can onboard new developers   and bring them up to speed to the project very  quickly. You also have enhanced reusability.   When components are focused on a single task, they  can easily be reused across your application. And   even across different projects. I do this quite  a lot. I make all of my components super simple,   super reusable. And I can easily use them  across my entire application. And I even   have some components that I use across different  projects. It also facilitates testing, right?   Smaller components with a single responsibility  are much easier to test because you have fewer   cases to consider. If you have a component that  only does one thing, you just have to write one,   two, or three tests, then make sure that it does  that one thing correctly. And also, it helps   improve the maintainability of the codebase and  of the project. And then when you have bugs, and   trust me, you are going to have bugs, it's kind  of inevitable to get bugs in a React application,   you will be able to pinpoint the issues much  faster because your code is well-organized. If you   have one of those bugs that is really difficult  to reproduce, it only happens in specific   use cases. If your code is well-organized,  it's made up of multiple small components,   you can find the actual components that are  related or at least close to where the bug is   happening. And chances are, that's where the bug  is going to be, right? This helps a lot with that. So in summary, the single responsibility  principle is actually quite simple:   just build your React components with  a single responsibility in mind. And   as soon as you need to add something else to a  component, just create a new component instead,   and combine them together, and then build  your application following that principle. So now let's put this into action and look at a  component that violates the single responsibility   principle, and then refactor it to make sure  that it conforms. This is the ProductsPage   component. And as the name implies, it is the  component that renders the page where we show   the actual products; this component violates  the principle because it does a lot of different   things. So let's look at some of those parts and  see exactly what this component is trying to do. So first, we have this piece of code here, which  actually fetches the products from this fake store   API. And this is doing it using the `useQuery`  hook from React Query, it's setting the query key   manually, it's creating and implementing the query  function directly in this component. And then   it's also accessing the `data`, `isFetching`, and  `error` properties that you get from React Query. Then in the actual JSX, we have an h1 tag that  says "Products Page," right? Simple enough. Then,   if `isFetching` is true, we're actually showing  this loading paragraph here. If `error` is true,   so if we have an error, we're rendering a  paragraph that says "Something went wrong."   And then if we have products, actually if we  have multiple products, we're mapping over the   products and rendering out each individual  product with this piece of code right here. So, this component is violating the single  responsibility principle. Now, before we   actually start refactoring this to make sure that  it conforms, let's first define what the single   responsibility of the ProductsPage is, because  it may not be that obvious. To me, this is a page   component, right? It has "page" in the name, it is  the component that renders the products page. And   as a page component, its responsibility should be  to take all of the different pieces of code across   the entire codebase and put them together, arrange  them together in such a way that it can render its   actual contents; that it can render the actual  products that we want to see in our UI. That's   its responsibility. However, it is not responsible  for actually implementing each of those individual   parts. This component should only take those parts  that are already there, they're already created   and managed by other components or other hooks,  and it should just take them, put them together,   arrange them, and then use them to actually  display the content that it should display. So now we're going to refactor this component,  keeping that responsibility in mind. And let's   first start with this piece of code right here,  where we're actually fetching the products. The   first thing to know is that we actually need to  fetch the products inside of this component right   here; we need access to the products to be able  to display them in our UI. But we actually don't   need to implement the fetch, the way that we get  those products, in this component right here. So   we don't need to use this `useQuery` hook inside  of this component. Even worse, we shouldn't set   the query key of "products" in this component. And  we shouldn't have to define our own query function   in this component as well. If we want to follow  the single responsibility principle, we should   actually extract this entire thing into a custom  hook and use that in this component instead. So let's actually do that. Let's come here to  our sidebar, let's create a new folder for hooks,   we're going to call this hooks. And inside of  here, we're going to create a new file. And we're   going to call this `useFetchProducts.ts`.  I'm going to create here a custom hook,   `export const useFetchProducts`. And  I'm going to come back here to the   ProductsPage component. And I'm going to  take all of this `useQuery` code here and   move it over to the other custom hook.  So I'm going to come here, paste it,   and I just have to import all of the things that  are missing. So that's `useQuery`. And also,   we have to import the `Product` type. And then I'm  just going to return the `useQuery` hook like so;   I'm going to do `return useQuery` and save. This  will allow the ProductsPage component to use this   custom hook instead, which actually returns  the same thing as we had it before. But now,   it's extracted in a custom hook. And  this custom hook also, by the way,   follows the single responsibility principle; it  only does one thing, its only responsibility is to   actually implement the fetching of the products,  which means setting the query key, which means   setting the query function and being reusable  enough that if we have any other component in   our application that also needs to fetch the  products, it can use this custom hook instead. Now, I do want to mention that technically, this  custom hook is doing two things; it is managing   the actual fetch of the products, but it's also  defining the function that makes that fetch,   this `fetchProducts` function here. Ideally,  this should be separated into another place,   perhaps an API folder, so that you can really keep  the single responsibility principle. And actually,   what I'm going to do is I'm just going to  move this here. And I'm going to create my   own function; I'm going to call this `const  fetchProducts`. And this is going to be an   asynchronous function. And let's see if I can  just paste this code here, `return` like this,   there we go. And now I can take this and  just give it directly to the query function:   `products`. So that now we have  a further separation of concerns,   this `fetchProducts` function is only doing one  thing; it's implementing the actual fetch. And   then we're combining it in this custom hook. This  is correct. Although in a real-world application,   you might move this into a different  folder, I'm not going to do it now,   just because I'm lazy. But I will put a comment  here, `// TODO: Move to API folder or equivalent`,   just so that it's clear that this doesn't really  belong here, because this is a custom hook file. And then we can come back to our ProductsPage  component and get rid of all of this code here   and replace it with our custom hook. So I'm  going to import `useFetchProducts` and call   it like so. And now this part is conforming to the  single responsibility principle because we're not   actually implementing the fetch, we're just using  the piece that has been implemented elsewhere. Now let's move on to the second part, which  is this part right here, line 21 and 22,   `isFetching` and `error`. What we're doing is,  yes, this ProductsPage has the responsibility   to decide that it wants to render something else  when `isFetching` is true, and that it wants to   render something when there's an error. However,  it's not its responsibility to actually implement   the UI for those states, for the loading and  error states. That implementation for both of   these should go to separate components so that  we conform to the principle. So let's do that.   Let's refactor it. Let's come here, let's make a  new folder called components. And inside of here,   we're going to make two components. The first one  is going to be `LoadingDisplay.tsx`. And I'm just   going to create the component `export default  function LoadingDisplay`. And I'm going to let   Copilot be helpful and complete it for me. And  then I'm going to create the second component,   `ErrorDisplay.tsx`. And I'm going to do `export  default function`, let's see, Copilot...   `ErrorDisplay`, and like so. And then I'm going  to put `return "Something went wrong."` And then   I'm going to go back to ProductsPage. And instead  of actually implementing the UI for `isFetching`   and `error`, I'm going to use those components  instead. So come here, import `LoadingDisplay`,   like so, and replace `error` with `ErrorDisplay`,  like this. The benefit of doing it this way is   that now we have a separate component for the  loading, we have a separate component for the   error. And if we ever want to reuse these across  different places, which honestly, we probably   will, we can just use those components instead and  not have to redefine and reimplement the actual   UI. And you might think to yourself, well, why do  we have to do that, we created a whole component   just for one line of code. The thing that you have  to know is that in these videos, in these tutorial   videos, I keep things very, very simple. In a real  application, you're going to have a lot more than   that. Just think of a basic case where you have an  error, and you want to have a button to try again,   to refetch that same request; that button would  go inside of this component and not inside of   this ProductsPage. Same for the LoadingDisplay,  right, you might have some complicated animation,   some loading spinner, and you don't want to have  to reimplement all of that every single time,   you create a custom component  for it and just use that instead. And finally, we have this last part right here,  which is the products' part. If we have products,   we render a div, and then we map over the products  and render out each individual product. Again,   it's not the responsibility of the ProductsPage  to actually implement the UI for rendering   multiple products. Yes, it needs to decide  that we want to actually show some products,   but it has to defer the actual implementation of  the UI logic to a different component. So what   I will do is I will come back here and I will  create another component, we're going to call   this one `ProductList.tsx`, then we're going to do  the same thing again, `export default function`,   `ProductList`. And that is going to take in the  same code as we had before. So I'm actually going   to copy all of this code, including the div,  you'll see why in just a moment, `return`,   and then paste this right here. Now we have  a problem, we actually don't have `products`   defined anywhere. So we actually need to have  this ProductList component, take in `products`   as props. So we need to first define our type.  So `type ProductListProps` equals `{ products:   Product[]; }`, and here we go, let's get rid of  this, import `Product`. And we need to give it   here the props `products: ProductListProps`, and  this should be fine. Now we no longer have any   errors. And then in the ProductsPage component,  we get rid of all this and we replace it with our   new custom component `ProductList`, import  this, give it `products`. And there we go. And already, just with this, you can see how much  code we actually removed from ProductsPage. This   component is now super easy to understand,  super easy to work with, and super easy to   manage. But we're actually not done, we have this  ProductList component, which doesn't follow the   single responsibility principle, because  it renders out the actual products, but it   also renders out the UI for a specific product. So  this part has to go in another separate component,   which we're going to create right now, let's  come back here, create a new file. And we're   going to call this `ProductCard.tsx`, do  the same thing, `export default function`,   `ProductCard`. And this one is going to take the  code that we have here, remove this, paste it,   return, I just lost that code. Let me just do it  again. There we go. And there we go, put it here,   save this. And now again, we also need to  give access to this component to `product`.   So we have to define some types. So we'll do  `type ProductCardProps` equals `{ product:   Product; }`, import `Product` and give it here  as its props, Copilot is being very helpful.   And now this `ProductCard` follows the single  responsibility principle, it only does one thing,   its only responsibility is to take a product  and to render out the actual UI of that product. In the ProductList, we can remove all of  this and we can replace it with our own   custom component that we just created.  So `ProductCard`, and then we give it   the `key` and the actual product. And now  this `ProductList` component also follows   the single responsibility principle, because it  does only one thing, it renders out a list of   products and it defers the actual rendering  of a specific product to another component. Now, you may be asking yourself why we have this  `div` here. And the reason why we have it is that   in a real-world application, you're probably  going to have something like this `className`,   you're going to do `flex`, and then let's say  `gap-4`, right? These are the styles that this   component needs to actually render out its  products, right? This is the responsibility.   These styles, they belong in this component;  they don't belong in the parent component, in the   ProductsPage component. And they also don't belong  in the individual ProductCard component because   there's no way that you can actually make this  work. This is the component that renders a list of   products. And these are the styles that it needs  to do that. So that's why we have this `div`. And also, going back to the ProductCard  component, one could make the argument   that this part right here should be a  separate component, which really it should,   I just chose not to do it to keep things simple.  Because this is the seller, this is a user,   this is no longer a product, right? So we would  ideally have a component that renders out a user,   perhaps we could call this the `UserCard`.  And the reason why you would want to make   this into another component is because the  `UserCard` might use the `UserAvatar` component,   might do some fancy logic to show the display name  of the user, maybe the display name comes from the   first name and the full name, and you want to  put them together. And all of those things make   sense in a separate component to not pollute the  responsibilities of this ProductCard component. And now with this, I mean, just look  at this component, right? Like this is   beautiful. It's very clear what this component  does, you know that it fetches the products,   then it renders out some loading if there's a  loading, if there's an error, it renders out   the error. If you actually have products, it also  renders out a list of products. It's super easy to   understand. I can give this to any developer that  has never seen this before. And they can quickly   understand where each thing is and how to actually  be productive. If they need to make some changes. If you enjoyed this video, and you want  to see more videos just like these,   make sure to leave this video a big thumbs up.  You can also click here to subscribe. Or you   can click here to watch a different video of  mine that YouTube seems to think you're really   going to enjoy. And with that being said, my  name has been Darius Cosden. This is Cosden   Solutions. Thank you so much for watching, and  I will see you in the next video. Ciao ciao.
Info
Channel: Cosden Solutions
Views: 38,332
Rating: undefined out of 5
Keywords: react tutorial, react crash course, react developer, learn react, react hook, react hooks, react hooks tutorial, programming tutorial, react hooks explained, computer science, tutorial for beginners, react component, learn programming, web development, frontend development, coding for beginners, simple code, easy programming, react, react native, react design patterns, design patterns react, design patterns in react, single responsibility principle
Id: tLPi3SPqUSE
Channel Id: undefined
Length: 16min 50sec (1010 seconds)
Published: Mon Jan 22 2024
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.