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.