I'm glad that frontenders are starting to use the principles of good design, however, I can't help but point out some mistakes in this video. I just don't want the newcomers to be misled. SRP: Dividing a component into sub-components is not exactly what this principle says. Such a division does exist, but it is called "a function should do only one thing". SRP talks about actors and the changes they have on the component. The division into sub-components is part of the implementation of this principle, but the motivation comes from the reasons for the changes. The component turned out to be clean anyway, so the problem is only in the definition. OCP: The very idea of closing the button component for modification was great, but the unfinished implementation led to an even bigger problem. Before refactoring, we had a great abstraction + encapsulation: we passed the button type as a string, and the button itself decided which icon to substitute. After refactoring, the abstraction simply disappeared, turning the button component into a humble component. Now we will pass an icon to the component, but we will still have an abstraction in the form of a button type in our head. This violates the DRY principle, and when we want to change the icons of all the "back" buttons... good luck finding them all. After closing the button for modification it was necessary to create, for example, the `ButtonFactory` class. It would change when adding new button types (thereby having one responsibility -> SRP), the button would be closed for modification, and all button components would be created only through the `ButtonFactory`. That's the way it had to be done. LSP: The advice is very helpful. It was possible to tell more about the inheritance of a component from another component, if they are made in a style with classes, and not through a hook. ISP: The definition of the principle is not quite correct, or rather not complete. The definition goes like this: "Software modules should not depend on things they don't use." This greatly expands the effect of the principle and removes the word "interface", which may confuse someone. It was also possible to show how component A uses component B, where B violates SRP, and when B changes for a reason that A should not depend on, it breaks A, although it should not have. The example with props is useful, anyway. DIP: Additional examples have already been written in the comments/ Thank you for bringing up such important topics for the frontend community. I hope my additions will help developers better understand this topic
Thank you for the great comment! About OCP: from my perspective his realization is good if he wraps button component into backButton and forwardButton components. Factory is not necessary. Also using SOLID in FP is very strange idea. Single responsibility principle is good, but for another principles FP has own match more suitable ones. Such as purity, immutability, higher order functions and etc.
Thank you for the good point. I felt like you took it from my head. It seems like the tutorial is a bit superficial, so for dev’s who are not aware about principles would not understand from this tutorial either. Especially, SRP and OCP. Diving the component to the smaller parts, business logic from rendering for sure it looks like refactoring, but not fully describes the SRP. Basically, this could be done even without decomposition.
On OCP I disagree highly. Every project I've been in, Buttons especially turn into monstrosities that take a massive list of props. Props might depend on each other and slightly change code execution in the original design pattern. Ie "in loading state, the small button should not have an icon". Injecting components and making Buttons dumb takes back control. This single change principle may be good in some cases, but I've never been in a situation where replacing a prop was a huge issue. The issue is rather that some highly specific use case requires you to add a new prop which changes the execution slightly in the component and risk having your buttons break in unexpected ways. A ButtonFactory wouldn't fix this issue I think, it would just make it easier to create specific types of buttons, but not really help with handling states.
@@rumble1925 Exactly. The ideas of the client varies and I don't know, if they jerk or something, because the feature are everything, but not with single responsibility... the frontend just needs to be simple CQRS, but we went from static sites, to virtual dom, to SPA, MPA, and in 2023 we are like, fetch small js footprint, and probably people will realise that frontend is just for dispatching actions nothing more...
For the last principle (D), another good example is Create vs Edit forms. In our application, we have 2 areas where these forms are shared, but their submit logic is different for each, so we've taken the exact same approach by extracting the form itself into its own component, and then move the submission logic (React Query mutations), into two separate parents, CreateForm and EditForm, where the actual form component can be configured there. It's a very nice separation of concerns: UI vs. request logic.
@@davidmendoza1477 In our case, we're using Tailwind CSS, so composing different styles on the same form component by forwarding props down is a breeze. If it's a case where each form needs unique enough styles, then I would break up the form into subcomponents and use the "role / variant" technique to distinguish between Create/Edit styling, especially if the core logic for the form's state, validation, and behaviors are pretty much the same. In any case, taking a mostly composable approach to architecting something like this will keep you from painting yourself into a corner.
@@docmars wow that's a great idea. I'm currently building a simple "web store" with a simple CMS using react and firebase and i happened to think about reusing the form for creating products when editing. But then i wanted to style them differently and ended up with a css nightmare in my hands. I will refactor it to use the "role/variant" technique you mentioned. Thanks a bunch. 😊 note: im just a beginner, so the tip is really appreciated.
@@davidmendoza1477 Best of luck, and have fun! Keep in mind, the role/variant approach does go against one of the other principles in this video, but don't view it as a hard rule. I generally use variants for commonly used components that share a lot of logic, but need very different configurations to alter how they're styled/displayed, in order to keep things DRY (don't repeat yourself). A great example for variants is a Tabs/Tab component set. Let's say you have 4 or 5 completely different ways to display tabs in your app, but they all share the exact same functionality to handle switching, state, events, etc. -- using variants lets you share all those things while giving you the power to change their HTML/CSS completely, without being forced to use the same structure with slight style tweaks. You can go overboard and change every aspect without duplicating core behavior everywhere. And with all of these handy architecture rules, just remember to do what makes the most sense... don't tie your hands too tight with them. Break the rules when you need to. Worst case scenario, you learn something about your architecture that you can refactor or improve later. No mistake is permanent.
something to keep in mind for beginners. It's ok to write 'ugly' code in the start. Sure, write a +100 lines in a single script, THEN start splitting it into different parts so that later it's easier to come back to
That is something I'm currently learning, I find it extremely difficult to pre-plan how my code is going to look, especially when I'm still new and figuring out exactly what is needed. I find that, first, do it ugly then break it up I work faster. Ofc I'm not planning to do this forever.
True. Especially in real work scenarios where time preassure aka delivery is more important than code quality. Although I would add that every time we create a piece of code, just add some comments at least with your own ideeas for improvement when time comes.
I am preparing for the system design and OOP coding session as a frontend. I keep finding myself forgetting what I have studied for OOP principles as though it makes sense in theory, I didn't have practical examples of why they are needed (to study lol) for Frontend. This video really helped me grasp what SOLID means to frontend! Thank you for your great work!
Thank you because these concepts are usually explained in other languages without any connection with React. Explaining them with good examples through React helped me a lot.
Serious. I have been using this principles in my react code and I had no idea I was using them. I mean I never knew there was something called SOLID principles but because I have been coding for long and I always come up with ideas to make my code clean, I kind of knew about this but never thought its a principle out there that's been used by the community. Wow
I agree that components are like objects in the OOP but in React we mostly use function programming, and IMO some SOLID principles are specifically target bad object hierarchies. Developers need to think carefully before blindly applying the following suggested interpretations: O - open-closed principle. In functional programming we do not use extension at all we always use composition of functions, higher order functions etc. The example which was shown IMO does not work well to demonstrate it. The Button which you demonstrated initially was probably not just a generic button but had a purpose to encapsulate some logic to be a NavigationButton. It had a property role which is an enumeration of all possible valid variations associated with particular icon. When you changed that to a generic button with slot 'icon' you just moved that logic upwards in the component hierarchy and probably broke an S principle somewhere else. Potentially multiple places would have to choose the icon, which is even worse because now someone can use a different icons for same role. So, you would probably still have a common component like NavigationButton with enum, which brings us to the square one. L - LSP (almost Lumpy Space Princess ;)) OOP principle as you also mentioned looks weird in functional code. Probably just bad example, but passing through all of the properties from the underlying component is not always right thing to do. In the particular example the usage of the native input is just an implementation detail of SearchInput. It could be a valid intention to restrict some of the capabilities of underlying component so that main component cannot be abused. Passing rest props allows overriding all the classes on the input, which would be arguably incorrect.
This was my immediate thought too, before even watching the video. React takes a functional programming approach at its core. The SOLID principles are great for object oriented programming (OOP) because OOP is so complicated and easy to screw up that we need to have all these complicated philosophies just to try and keep developers from making a giant mess (which is still pretty easy to do even if you're trying to follow SOLID principles). It's hard for me to even want to watch this video when the fundamental premise seems to be entirely offtrack. Skip OOP with React entirely and you'll likely find that not only do you not need SOLID, you'll write code easier, faster, and of higher quality.
@@baka_bacaThese principles can and should be applied to functional programming as well. Another shocker: just that React is function oriented does not make it functional programming. It can take inspiration from, but its not an is-a relationship. :) If you think you know functional programming, take a gander at Haskell or Scala or F#, if you understand all of the concepts, then perhaps you are programming in that way. But js itself is not even a functional programming language, it lacks important features like unlimited recursion.
" In functional programming we do not use extension at all we always use composition of functions" OOP should, when done right, mostly use compositon and use inheritance sparingly. And just the fact that composition solves the open closed principle for some cases, does not mean it does not apply. (it literally does by composing properly) You can break the principle in FP by giving a function too much responsibility so you first need to split it up in order to compose it. (by modifying its not closed for modification, and not open to extension since you can't compose it)
This is great! I understood the SOLID principles before with classes in pure OOP languages like C# or Java, but it was hard to apply them to Component Frameworks. I never used React but having the SOLID principles explained with React makes it really easy to apply them to other Component Frameworks that I use, like Svelte! Also, I rarely see tutorials that show examples that are this good with beeing realistic and also accurate enough to exactly display what you want to teach. Great Job!
2 роки тому+2
It's hard to apply them because you shouldn't do it in the first place. Try functional programming instead..
just to extend what @kuba said, there are separate design patterns and principles for Functional Programming such as hoc, pure functions, etc... React Developer should study and apply these in first place.
@ Redux before RTK proves that you'll end up with the same crap if apply this indiscrimated (altough i like FP and Redux and HOCs). Its not like you shouldn't do it in first place, and just need to go for FP, is more like, if you use FP, you'll end up applying SOLID principles anyway. thinking in terms of composition and pure functions, lead to small modules, and SRP bcoz functional programmers tend to like the lego code. OCP is acchivable with HOFs and HOCs. Its pretty ease extend some feature in a function with this and composition. LSP is pretty useless since you don't have inheritance in FP, but if you use TS its just follow the subtyping, covariance and contravariance when you 're doing type driven development and you'll be fine. if you just have functions, and not an agraggate at all, you tend to just use what you need, like the ISP says you to do. And DIP is fairly like you've been doing in OOP, specially if you into clean arch or ports and adapters, that is the common arch in FP systems bcoz the objective is almost the same, get rid of the side effects and mantain the core pure.
Great video. Past few day's I've been working with a farely big project. I just started to get overwhelmed by the amount of code i've written and how i'm going to manage it in the futue. So, decided to do a little study on clean and maintainable code. You'r video was great. Now i'm going to read the book. :)
Amazing videos ! Theorical principles alone can be quite tricky to conceptualize. Here. Every step make sense and you grasp the perfect case each time to explain the benefits of this implementation Great job. Thanks :)
Senior engineer here, and I came in very skeptical about this because I think of solid principles as an OOP thing. But I found this to be a surprisingly good way of thinking about solid code in a functional application. One thing I am curious about though... why do you deconstruct your props on the line after they are declared in the argument of your component/function? I would argue that this pattern can cause issues. For example, if you had an onChange prop for a input-field wrapper, where you actually abstracted away the event itself and just passed the target.value back to the onChange function prop that you took in, then you might do something like this: onChange(e.target.value)} {...props} /> But when you spread ALL of the ORIGINAL props, you'll override your onChange here. Whereas if you deconstructed your props in the function argument like this const Component = ({ onChange, ...props}) => ... then you wouldn't have that problem. TypeScript would catch this error, but it can still cause a headache for no reason.
This is a non-issue. It's only a matter of coding style/preference. You can simply deconstruct the same way inside the component. The same rules apply there. e.g. const { onChange, ...rest } = props ... onChange(e.target.value)} {...rest} />
for the LSP I think one way to summarize what you're trying to say is: the custom searchInput component and the regular input component should be replaceable So, by passing all the "input" props to your "searchInput" you could simply exchange one for the other and the code would still work
Thanks a lot for this video ; as a newcomer to react with a java background it's refreshing too see how all these oop principles can be reused. You provide nice concrete examples of issues I was already confronted with!
For the last principle (D), I would note that this is especially useful for testing and for allowing to not tie up your code with specific concretions. For example, you are working on an application that has Firebase login. During development and testing you don't want to call Firebase each time with a test account but rather you can just inject a mock auth provider instead, making tests run a lot faster. Equally important, if in the future you want to change your auth provider, you just implement a new concretion of the new auth provider (a single class) and then all of your application works fine with it.
@@ruyvieira104 It is reasonable to think like that if you do not have too much experience with testing and although End-to-End or Integration Tests are important, they cannot be run all the time. Fast BDD or unit tests should be run all the time to make sure what you just coded or refactored didn't break anything. If you have to talk with external systems (that could also block you or charge you if you make too many requests e.g. Firebase auth) or even local databases each time you run your tests then this will slow you up too much and you won't want to constantly run your tests. Or imagine you want to run tests that involve calls to Google's Directions API. If you mock the API is it really fast and free, whereas if you use the real deal every time you will be paying $5 per 1k requests and with a large team that can really add up.
@@Bitloops firebase has emulators for pretty much everything + testing won't tell you if your expected behavior isn't broken or just wrong in first place. you could be doing a lot of db requests in a loop and since "it's working", your tests will preserve something which shouldn't be.
@@ruyvieira104 the emulators are great and have used them but you are missing the point. The tests you are talking about are either integration or end-to-end and while useful and while they should be run before each release to catch the kind of errors you are talking about, they don’t need to be run after even single change you do in your code. It is like saying that there is no value in unit tests and that you should only run end-to-end and integration tests. If you believe that unit tests are useless there is no point to this discussion. If you believe that unit tests are not useless but you believe that you should be using emulators (if they exist like in the case of Firebase but for many other systems they don’t) to run unit tests then you can be sure that you should be using dependency injection on those tests and not emulators. Emulators might be great to support bad design that doesn’t support DI but unit tests should never depend on external systems to run be it actual systems or emulators.
@@Bitloops but there's very little value in unit tests. You only "need" them if a) you have some crazy and extremely convoluted salad of "controllers models views adapters interfaces repositories" (which you shouldn't have in first place, as it just makes it easier to work a lot more while accomplishing a lot less). In this case, unit testing will be testing an entire structure which you don't need in first place. If you're so afraid of "breaking" things, don't use so many "layers of abstraction" and arbitrary code structure in first place. b) you're doing some weird math calculation or hashing function and want to see if you can improve its performance without breaking it. Other than that, you're just wasting everyone's time. Try not making your codebase a minefield and you won't be so worried about breaking things to the point where you need to "unit test" to see if 1 + 1 is still equal 2. FIrebase emulators exist so you can have meaningful (as opposed to useless) tests. You can write a script that will make a bunch of requests to a guinea pig to see what the actual effects are (miniature black box testing). If you're not using firebase, you have access to docker and docker composer to perform black box tests.
I would add that instead of fetching data with fetch, axiom of w/e + useEffect and throwing it into a hook, start using React query which does that and MUCH more for you.
"...Make no mistake, there is a principle like that. A function should do one, and only one, thing. We use that principle when we are refactoring large functions into smaller functions; we use it at the lowest levels. But it is not one of the SOLID principles - it is not the SRP. Historically, the SRP has been described this way: 'A module should have one, and only one, reason to change' " (c) Clean Architecture by Robert C. Martin
Having to deal with code architecture that did not implement the "Open-Closed" principle have been a long-going struggle in my 4 years of working as a software engineer. These are one of the most important ideas that I'm trying to make sure we follow in our team, but only today I learned that it has a name 😅 So thanks for that
Damn, without knowingly, I am actually applying these principles. I came to realize to use practices like these while building my very first production level application, where you need to handle complex components and make them easy to reuse and readable.
Only push back that I would have is that SOLID principles aren’t meant exclusively for OOP. In fact, Uncle Bob makes that explicitly clear in his book Clean Architecture along with providing some examples of how they might apply in other non-OOP paradigms.
Great video! The only problem I see, about OCP principle example (09:13), is that you turns the icon possibilites too generic (ReactNode), and sometimes it can turn out a problem, to just not declare the static possibilites of icon values that the Button component icon prop can receive as value. In that case, you can use DI to create a kind of interface and every single icon shall to implement that interface (I'm using the icon example, but it's better to think in any other cases where the prop is not a ReactNode).
13:51 Alternative suggestion: use `{product: Pick}` for the thumbnail props. Your principle is kept, but a) you don't break existing usages, b) you're much more opened to using other Product's properties in the future, and c) it makes more sense from a consumer perspective, that might have a Product object and doesn't want to be aware of the internal implementation of the Thumbnail (and what it needs) Note: in this particular case your way might make more sense, because you might want to use "thumbnail" for stuff that are not "product", but it's still a nice option that makes a lot of sense in many situations.
I don''t see the point on splitting code just for splitting responsibility in subcomponents that have no chance to be reused anywhere else. It just makes the code harder to read and understand, layers behind layers, for nothing. Specially with UI components which structure can change any time just because of a new trend in UI design...
@@grenadier4702 i rather "decompose" only when either there is a potential for reusing subcomponents or when there is a clear benefit on readability. For example when a component becomes too large or a process have clear steps that are easier to understand separately...
Always wait for a reason to engineer something past business requirements. "Because principle" does not count as a reason. This comment deserves to be made into a UA-cam video.
There's a convention followed in some companies that if your component code exceeds some specific number of lines let's say 100, then that's a signal that component can be further broken down
Amazing cideo! I think the solid principles are a double-edge sword. Lots of abstractions can make a codebase harder to understand for a new dev. So I try to be pragmatic and allow to deviate from SOLID when necessary.
Even better for DIP is to take a services approach and maintain the logic out of component scope so you don't need to do the useCallback dance as you have stable function references. Which also solves other issues and promotes better application design.
The code examples in this video should be taken as just that...examples. They're effective at getting the concepts across. They themselves shouldn't be taken as rules. For example, passing all props of native element from a custom input component (there is good reason to restrict props in many cases, that's why it's called abstraction).
these are they very first things a new software engineer must learn...frameworks, technologies, languages etc come afterwards....SOLID, DRY, reusability, testability, extensibility, ease to read/understand code, these principles must be the guiding light for new software engineers...
those are really awsome videos. I just learned java from ground up and to see these principles also applied to react makes so much more sense now! some constructive criticism for you videos: when you hav a video that has a lot of text like code windows and you put some quotes" like showing a sentence that summarizes the principle you are talking about, it is really hard to read. may put a box or something behind that text you overlay on top :)
Great video. On the example shown for the Interface Segreggation Principle you could say that doing that (imageUrl prop instead of product) also enforces the SRP because now the Image component doesn't have the responsibility of knowing the shape of a product object and what its imageUrl attribute is called. After the change now it doesn't even know what a product is, and that's beautiful.
Great explanations! As a "native" JS developer I always struggle to understand/translate those OOP principles since I use a lot of functional programming just as React does. This video is pure gold, I will be glad if you make some others videos about things such as design patterns. Thank you!
I think it's important to point out (especially for new developers) that "responsibility" in SRP is not synonymous with function. It would be easy to confuse a responsibility with a single function. The word "responsibility" in the context refers to the sate of the object. In true SRP fashion, a class, component, module, etc, should only have 1 reason to change. That reason is its responsibility. A good example of this would be an "Employee" class in an HR application. Let's say that class is responsible for holding employee information, like name, age, start date, department, etc. Changing any of those parameters is still within the realm of the class' responsibility. However, if an employee's performance management was added to that class, then that would be a violation of SRP because the class is now responsible for the basic employee information as well as handling the employee performance. The SRP method would be to create two separate classes, EmployeeData and EmployeePerformance.
I like the idea of trying to apply OOP principles to functional programming, even though obviously some of these are subjective interpretation of the original OOP principles.
Any reason you don’t destructure your props in the component definition? your approach prevents linters from finding unused props (which means there’s a bug usually)
I do the same, i always make an interface for the props, then do props:Props, and deconstruct them on the first line of the function. My linter catches these bugs.
I find typescript reduces the ability to read code, increasing cognitive load. Our team stripped Typescript from most of our projects and are very happy we did that
your useRateFilter hook is purely an alias over useState() since handleRating is identical to setFilterRate. So beside the name clarity benefits are there any principle that I miss behind this ?
Thank you for this video. It's good and rare to see someone explaining SOLID principles in the React context. I want to make one addition below. For the Liskov Substitution Principle, we should also make the current Tailwind classes overridable by using an npm package like tailwind-merge and combining classes that come from parent to the already existing classes in child component by making each individual class overridable. Otherwise when className prop is inside the restProps that are spreaded, a single class like "p-1" passed to the parent would reset all the classes the child component has. Also, instead of passing creating an `isLarge`, you can just pass the tailwind class `text-3xl` to the parent. Otherwise, each time when the designer wants to add a search input, small, big, medium, extra large, etc. you would have to define a new prop which breaks the open closed principle.
hey , im an amateur in react , can you explain why are you using interfaces for the props to create an object instead of directly using the props ? (e.g in 13:07 )
each parameter of a react functional component (the functions you create to represent your components) is wrapped in an object. you cannot, for example, pass a raw string/number/array to a component. you always have to pass the prop name and then the prop value (just like an object). with that in mind, when you use an interface, you are not creating an object, but annotating the type of each prop/key of that objet.
9:20 what if the icon for a specific button must change, now we have to go through the whole source code to change the icon everywhere, any ways to avoid that?
At 5:44 you're suggesting whenever you use multiple hooks in a component like useState and useEffect, you then immediately think that you should create a separate custom hook. In my opinion that's not what hooks are for, they should encapsulate project-wide reusable logic. And as I saw, you're only using that custom hook in one component to get the list of products so that logic could stay inside that component. What do you think?
Disagree. Its much cleaner to extract it out into a custom hook. Next you'll be telling me that unless a piece of code is used elsewhere it's not worth extracting into its own function.
@@archrodney that's a silly assumption to make. A hook is just a function that contains react state and has a couple additional rules around it because of said state. Are you telling me you don't ever extract code into a function if you don't use it anywhere else? It's about readability and maintainability. It's far better to extract complex hook logic into a custom hook you can test than just make a monstrosity of a component with 5-6 hooks for different stuff all weaved into each other. Perfect way to write tech debt from day 0.
@@ChillAutos Of course you can (ab)use hooks for whatever things you'd like but that's like using a element to create a layout. It's certainly possible and many people do that but that's not what it's for. So cleaning up shouldn't be your main use case for custom hooks either...
Should we create a custom hook in case we need to use fetch data only in one component? Just want to ensure cause my friend dev said it’s not necessary
The SOLID React Book is out! 🎉
solidreact.dev
I'm glad that frontenders are starting to use the principles of good design, however, I can't help but point out some mistakes in this video. I just don't want the newcomers to be misled.
SRP: Dividing a component into sub-components is not exactly what this principle says. Such a division does exist, but it is called "a function should do only one thing". SRP talks about actors and the changes they have on the component. The division into sub-components is part of the implementation of this principle, but the motivation comes from the reasons for the changes. The component turned out to be clean anyway, so the problem is only in the definition.
OCP: The very idea of closing the button component for modification was great, but the unfinished implementation led to an even bigger problem. Before refactoring, we had a great abstraction + encapsulation: we passed the button type as a string, and the button itself decided which icon to substitute. After refactoring, the abstraction simply disappeared, turning the button component into a humble component. Now we will pass an icon to the component, but we will still have an abstraction in the form of a button type in our head. This violates the DRY principle, and when we want to change the icons of all the "back" buttons... good luck finding them all. After closing the button for modification it was necessary to create, for example, the `ButtonFactory` class. It would change when adding new button types (thereby having one responsibility -> SRP), the button would be closed for modification, and all button components would be created only through the `ButtonFactory`. That's the way it had to be done.
LSP: The advice is very helpful. It was possible to tell more about the inheritance of a component from another component, if they are made in a style with classes, and not through a hook.
ISP: The definition of the principle is not quite correct, or rather not complete. The definition goes like this: "Software modules should not depend on things they don't use." This greatly expands the effect of the principle and removes the word "interface", which may confuse someone. It was also possible to show how component A uses component B, where B violates SRP, and when B changes for a reason that A should not depend on, it breaks A, although it should not have. The example with props is useful, anyway.
DIP: Additional examples have already been written in the comments/
Thank you for bringing up such important topics for the frontend community. I hope my additions will help developers better understand this topic
Thank you for the great comment! About OCP: from my perspective his realization is good if he wraps button component into backButton and forwardButton components. Factory is not necessary.
Also using SOLID in FP is very strange idea. Single responsibility principle is good, but for another principles FP has own match more suitable ones. Such as purity, immutability, higher order functions and etc.
Thank you for the good point. I felt like you took it from my head.
It seems like the tutorial is a bit superficial, so for dev’s who are not aware about principles would not understand from this tutorial either.
Especially, SRP and OCP.
Diving the component to the smaller parts, business logic from rendering for sure it looks like refactoring, but not fully describes the SRP.
Basically, this could be done even without decomposition.
On OCP I disagree highly. Every project I've been in, Buttons especially turn into monstrosities that take a massive list of props. Props might depend on each other and slightly change code execution in the original design pattern. Ie "in loading state, the small button should not have an icon".
Injecting components and making Buttons dumb takes back control. This single change principle may be good in some cases, but I've never been in a situation where replacing a prop was a huge issue. The issue is rather that some highly specific use case requires you to add a new prop which changes the execution slightly in the component and risk having your buttons break in unexpected ways. A ButtonFactory wouldn't fix this issue I think, it would just make it easier to create specific types of buttons, but not really help with handling states.
@@rumble1925 Exactly. The ideas of the client varies and I don't know, if they jerk or something, because the feature are everything, but not with single responsibility... the frontend just needs to be simple CQRS, but we went from static sites, to virtual dom, to SPA, MPA, and in 2023 we are like, fetch small js footprint, and probably people will realise that frontend is just for dispatching actions nothing more...
Thank you for these very helpful tips, cheerd to you good sir
For the last principle (D), another good example is Create vs Edit forms. In our application, we have 2 areas where these forms are shared, but their submit logic is different for each, so we've taken the exact same approach by extracting the form itself into its own component, and then move the submission logic (React Query mutations), into two separate parents, CreateForm and EditForm, where the actual form component can be configured there. It's a very nice separation of concerns: UI vs. request logic.
This would be a perfect example too
Its all fun and games until you want to style them differently.
@@davidmendoza1477 In our case, we're using Tailwind CSS, so composing different styles on the same form component by forwarding props down is a breeze.
If it's a case where each form needs unique enough styles, then I would break up the form into subcomponents and use the "role / variant" technique to distinguish between Create/Edit styling, especially if the core logic for the form's state, validation, and behaviors are pretty much the same.
In any case, taking a mostly composable approach to architecting something like this will keep you from painting yourself into a corner.
@@docmars wow that's a great idea. I'm currently building a simple "web store" with a simple CMS using react and firebase and i happened to think about reusing the form for creating products when editing. But then i wanted to style them differently and ended up with a css nightmare in my hands. I will refactor it to use the "role/variant" technique you mentioned. Thanks a bunch. 😊 note: im just a beginner, so the tip is really appreciated.
@@davidmendoza1477 Best of luck, and have fun!
Keep in mind, the role/variant approach does go against one of the other principles in this video, but don't view it as a hard rule. I generally use variants for commonly used components that share a lot of logic, but need very different configurations to alter how they're styled/displayed, in order to keep things DRY (don't repeat yourself).
A great example for variants is a Tabs/Tab component set. Let's say you have 4 or 5 completely different ways to display tabs in your app, but they all share the exact same functionality to handle switching, state, events, etc. -- using variants lets you share all those things while giving you the power to change their HTML/CSS completely, without being forced to use the same structure with slight style tweaks. You can go overboard and change every aspect without duplicating core behavior everywhere.
And with all of these handy architecture rules, just remember to do what makes the most sense... don't tie your hands too tight with them. Break the rules when you need to. Worst case scenario, you learn something about your architecture that you can refactor or improve later.
No mistake is permanent.
something to keep in mind for beginners. It's ok to write 'ugly' code in the start. Sure, write a +100 lines in a single script, THEN start splitting it into different parts so that later it's easier to come back to
That is something I'm currently learning, I find it extremely difficult to pre-plan how my code is going to look, especially when I'm still new and figuring out exactly what is needed. I find that, first, do it ugly then break it up I work faster. Ofc I'm not planning to do this forever.
Yes, everyone starts with this
True.
Especially in real work scenarios where time preassure aka delivery is more important than code quality.
Although I would add that every time we create a piece of code, just add some comments at least with your own ideeas for improvement when time comes.
The mantra is, First make it work, then make it right, then make it fast.
Make it work, make it testable, then make it look beautiful 😍. Just don't forget to write tests before starting to refactoring things around.
Finally, we have dev care about paradigms and solid principles, 2 thumbs up!!
I am preparing for the system design and OOP coding session as a frontend. I keep finding myself forgetting what I have studied for OOP principles as though it makes sense in theory, I didn't have practical examples of why they are needed (to study lol) for Frontend. This video really helped me grasp what SOLID means to frontend! Thank you for your great work!
Thank you because these concepts are usually explained in other languages without any connection with React. Explaining them with good examples through React helped me a lot.
Serious. I have been using this principles in my react code and I had no idea I was using them. I mean I never knew there was something called SOLID principles but because I have been coding for long and I always come up with ideas to make my code clean, I kind of knew about this but never thought its a principle out there that's been used by the community. Wow
I agree that components are like objects in the OOP but in React we mostly use function programming, and IMO some SOLID principles are specifically target bad object hierarchies. Developers need to think carefully before blindly applying the following suggested interpretations:
O - open-closed principle. In functional programming we do not use extension at all we always use composition of functions, higher order functions etc. The example which was shown IMO does not work well to demonstrate it. The Button which you demonstrated initially was probably not just a generic button but had a purpose to encapsulate some logic to be a NavigationButton. It had a property role which is an enumeration of all possible valid variations associated with particular icon. When you changed that to a generic button with slot 'icon' you just moved that logic upwards in the component hierarchy and probably broke an S principle somewhere else. Potentially multiple places would have to choose the icon, which is even worse because now someone can use a different icons for same role. So, you would probably still have a common component like NavigationButton with enum, which brings us to the square one.
L - LSP (almost Lumpy Space Princess ;)) OOP principle as you also mentioned looks weird in functional code. Probably just bad example, but passing through all of the properties from the underlying component is not always right thing to do. In the particular example the usage of the native input is just an implementation detail of SearchInput. It could be a valid intention to restrict some of the capabilities of underlying component so that main component cannot be abused. Passing rest props allows overriding all the classes on the input, which would be arguably incorrect.
This was my immediate thought too, before even watching the video. React takes a functional programming approach at its core. The SOLID principles are great for object oriented programming (OOP) because OOP is so complicated and easy to screw up that we need to have all these complicated philosophies just to try and keep developers from making a giant mess (which is still pretty easy to do even if you're trying to follow SOLID principles).
It's hard for me to even want to watch this video when the fundamental premise seems to be entirely offtrack. Skip OOP with React entirely and you'll likely find that not only do you not need SOLID, you'll write code easier, faster, and of higher quality.
@@baka_bacaThese principles can and should be applied to functional programming as well. Another shocker: just that React is function oriented does not make it functional programming. It can take inspiration from, but its not an is-a relationship. :)
If you think you know functional programming, take a gander at Haskell or Scala or F#, if you understand all of the concepts, then perhaps you are programming in that way. But js itself is not even a functional programming language, it lacks important features like unlimited recursion.
" In functional programming we do not use extension at all we always use composition of functions"
OOP should, when done right, mostly use compositon and use inheritance sparingly. And just the fact that composition solves the open closed principle for some cases, does not mean it does not apply. (it literally does by composing properly) You can break the principle in FP by giving a function too much responsibility so you first need to split it up in order to compose it. (by modifying its not closed for modification, and not open to extension since you can't compose it)
I know this is difficult for beginners to understand but it is totally worth it to learn. thanks for sharing.
This is great! I understood the SOLID principles before with classes in pure OOP languages like C# or Java, but it was hard to apply them to Component Frameworks. I never used React but having the SOLID principles explained with React makes it really easy to apply them to other Component Frameworks that I use, like Svelte! Also, I rarely see tutorials that show examples that are this good with beeing realistic and also accurate enough to exactly display what you want to teach. Great Job!
It's hard to apply them because you shouldn't do it in the first place. Try functional programming instead..
just to extend what @kuba said, there are separate design patterns and principles for Functional Programming such as hoc, pure functions, etc... React Developer should study and apply these in first place.
@ Redux before RTK proves that you'll end up with the same crap if apply this indiscrimated (altough i like FP and Redux and HOCs).
Its not like you shouldn't do it in first place, and just need to go for FP, is more like, if you use FP, you'll end up applying SOLID principles anyway.
thinking in terms of composition and pure functions, lead to small modules, and SRP bcoz functional programmers tend to like the lego code.
OCP is acchivable with HOFs and HOCs. Its pretty ease extend some feature in a function with this and composition.
LSP is pretty useless since you don't have inheritance in FP, but if you use TS its just follow the subtyping, covariance and contravariance when you 're doing type driven development and you'll be fine.
if you just have functions, and not an agraggate at all, you tend to just use what you need, like the ISP says you to do.
And DIP is fairly like you've been doing in OOP, specially if you into clean arch or ports and adapters, that is the common arch in FP systems bcoz the objective is almost the same, get rid of the side effects and mantain the core pure.
Great video.
Past few day's I've been working with a farely big project. I just started to get overwhelmed by the amount of code i've written and how i'm going to manage it in the futue.
So, decided to do a little study on clean and maintainable code.
You'r video was great. Now i'm going to read the book. :)
The BEST tutorial I have ever seen for a long time. Nice explanation and simple examples. Love it.
Awesome! I have seen this for the fourth time to understand it better. Kudos!
Amazing videos ! Theorical principles alone can be quite tricky to conceptualize.
Here. Every step make sense and you grasp the perfect case each time to explain the benefits of this implementation
Great job. Thanks :)
Coming after the Berlin React 2023, you were the only speaker interesting today. I subscribed as well, i'll be watching all of your videos :)
Glad you found the React Day Berlin talk interesting. More interesting content to come :D
Senior engineer here, and I came in very skeptical about this because I think of solid principles as an OOP thing. But I found this to be a surprisingly good way of thinking about solid code in a functional application.
One thing I am curious about though... why do you deconstruct your props on the line after they are declared in the argument of your component/function? I would argue that this pattern can cause issues. For example, if you had an onChange prop for a input-field wrapper, where you actually abstracted away the event itself and just passed the target.value back to the onChange function prop that you took in, then you might do something like this:
onChange(e.target.value)} {...props} />
But when you spread ALL of the ORIGINAL props, you'll override your onChange here. Whereas if you deconstructed your props in the function argument like this
const Component = ({ onChange, ...props}) => ...
then you wouldn't have that problem. TypeScript would catch this error, but it can still cause a headache for no reason.
This is a non-issue.
It's only a matter of coding style/preference.
You can simply deconstruct the same way inside the component.
The same rules apply there.
e.g.
const { onChange, ...rest } = props
...
onChange(e.target.value)} {...rest} />
for the LSP I think one way to summarize what you're trying to say is:
the custom searchInput component and the regular input component should be replaceable
So, by passing all the "input" props to your "searchInput" you could simply exchange one for the other and the code would still work
Thanks a lot for this video ; as a newcomer to react with a java background it's refreshing too see how all these oop principles can be reused. You provide nice concrete examples of issues I was already confronted with!
Dude, you’re awesome. Keep these types of videos coming. Learning a lot.
This video has saved me from a lot of pain 😭 thank you!
For the last principle (D), I would note that this is especially useful for testing and for allowing to not tie up your code with specific concretions. For example, you are working on an application that has Firebase login. During development and testing you don't want to call Firebase each time with a test account but rather you can just inject a mock auth provider instead, making tests run a lot faster. Equally important, if in the future you want to change your auth provider, you just implement a new concretion of the new auth provider (a single class) and then all of your application works fine with it.
>a lot faster
also a lot less useful
@@ruyvieira104 It is reasonable to think like that if you do not have too much experience with testing and although End-to-End or Integration Tests are important, they cannot be run all the time. Fast BDD or unit tests should be run all the time to make sure what you just coded or refactored didn't break anything. If you have to talk with external systems (that could also block you or charge you if you make too many requests e.g. Firebase auth) or even local databases each time you run your tests then this will slow you up too much and you won't want to constantly run your tests. Or imagine you want to run tests that involve calls to Google's Directions API. If you mock the API is it really fast and free, whereas if you use the real deal every time you will be paying $5 per 1k requests and with a large team that can really add up.
@@Bitloops firebase has emulators for pretty much everything + testing won't tell you if your expected behavior isn't broken or just wrong in first place. you could be doing a lot of db requests in a loop and since "it's working", your tests will preserve something which shouldn't be.
@@ruyvieira104 the emulators are great and have used them but you are missing the point. The tests you are talking about are either integration or end-to-end and while useful and while they should be run before each release to catch the kind of errors you are talking about, they don’t need to be run after even single change you do in your code. It is like saying that there is no value in unit tests and that you should only run end-to-end and integration tests. If you believe that unit tests are useless there is no point to this discussion. If you believe that unit tests are not useless but you believe that you should be using emulators (if they exist like in the case of Firebase but for many other systems they don’t) to run unit tests then you can be sure that you should be using dependency injection on those tests and not emulators. Emulators might be great to support bad design that doesn’t support DI but unit tests should never depend on external systems to run be it actual systems or emulators.
@@Bitloops but there's very little value in unit tests. You only "need" them if a) you have some crazy and extremely convoluted salad of "controllers models views adapters interfaces repositories" (which you shouldn't have in first place, as it just makes it easier to work a lot more while accomplishing a lot less). In this case, unit testing will be testing an entire structure which you don't need in first place. If you're so afraid of "breaking" things, don't use so many "layers of abstraction" and arbitrary code structure in first place. b) you're doing some weird math calculation or hashing function and want to see if you can improve its performance without breaking it. Other than that, you're just wasting everyone's time. Try not making your codebase a minefield and you won't be so worried about breaking things to the point where you need to "unit test" to see if 1 + 1 is still equal 2. FIrebase emulators exist so you can have meaningful (as opposed to useless) tests. You can write a script that will make a bunch of requests to a guinea pig to see what the actual effects are (miniature black box testing). If you're not using firebase, you have access to docker and docker composer to perform black box tests.
I don’t speak English but I decided consume your content to practice
I would add that instead of fetching data with fetch, axiom of w/e + useEffect and throwing it into a hook, start using React query which does that and MUCH more for you.
I think its better to keep things simple at start and expand with the scope.
Yup, custom hooks when it is not much reusable is just worse.
I have been waiting 5 years to see someone show something any good to SOLID that is not surpassed by writing easy to understand code. Lets see
thank you, just sending this message to drive engagement
Every new react developer should watch this video. Very useful. Thank you!
OOP is no React Thinking,
React is more functional Programming
@@kishirisu1268 Classes almost replace by hooks
I believe that you must create a video about react clean architecture , keep growing , you are good at this
"...Make no mistake, there is a principle like that. A function should do one, and only one, thing. We use that principle when we are refactoring large functions into smaller functions; we use it at the lowest levels. But it is not one of the SOLID principles - it is not the SRP. Historically, the SRP has been described this way: 'A module should have one, and only one, reason to change' " (c) Clean Architecture by Robert C. Martin
Having to deal with code architecture that did not implement the "Open-Closed" principle have been a long-going struggle in my 4 years of working as a software engineer. These are one of the most important ideas that I'm trying to make sure we follow in our team, but only today I learned that it has a name 😅 So thanks for that
Thank You, keep posting videos like this, with more tutorials
Can not be simpler with this explaination. Thank you so much
That's a SOLID explanation. Great stuff. Keep up
Damn, without knowingly, I am actually applying these principles.
I came to realize to use practices like these while building my very first production level application, where you need to handle complex components and make them easy to reuse and readable.
Only push back that I would have is that SOLID principles aren’t meant exclusively for OOP. In fact, Uncle Bob makes that explicitly clear in his book Clean Architecture along with providing some examples of how they might apply in other non-OOP paradigms.
Great video!
The only problem I see, about OCP principle example (09:13), is that you turns the icon possibilites too generic (ReactNode), and sometimes it can turn out a problem, to just not declare the static possibilites of icon values that the Button component icon prop can receive as value.
In that case, you can use DI to create a kind of interface and every single icon shall to implement that interface (I'm using the icon example, but it's better to think in any other cases where the prop is not a ReactNode).
13:51 Alternative suggestion: use `{product: Pick}` for the thumbnail props. Your principle is kept, but a) you don't break existing usages, b) you're much more opened to using other Product's properties in the future, and c) it makes more sense from a consumer perspective, that might have a Product object and doesn't want to be aware of the internal implementation of the Thumbnail (and what it needs)
Note: in this particular case your way might make more sense, because you might want to use "thumbnail" for stuff that are not "product", but it's still a nice option that makes a lot of sense in many situations.
a very helpful video, the content is simple understand, this is very useful
Fantastic video! Looking forward to view more!
Thanks, nice work, all examples make sence!
Would love to have more "code quality" videos
do this for every loop in a big project, good luck with that when debugging
nov 2023 and still a great video, thank you!
Man, thank you very much!! You are so good at explaining and sharing the core idea. ❤
I already have so much more knowledge to apply on my projects..
I don''t see the point on splitting code just for splitting responsibility in subcomponents that have no chance to be reused anywhere else. It just makes the code harder to read and understand, layers behind layers, for nothing. Specially with UI components which structure can change any time just because of a new trend in UI design...
On other hand, component "not-decomposition" is a antipattern and it'll hurt your team
@@grenadier4702 i rather "decompose" only when either there is a potential for reusing subcomponents or when there is a clear benefit on readability. For example when a component becomes too large or a process have clear steps that are easier to understand separately...
@@jacmkno5019 yeah, makes sense. That's what I do as well
Exactly! Early abstractions are a really bad thing. Be careful with what you watch, junior devs.
Always wait for a reason to engineer something past business requirements.
"Because principle" does not count as a reason.
This comment deserves to be made into a UA-cam video.
Thank you for your work - useful points for me!
There's a convention followed in some companies that if your component code exceeds some specific number of lines let's say 100, then that's a signal that component can be further broken down
Nice, good video. I had to look up #2 and 3 to really get it but I found the React examples useful.
Amazing cideo! I think the solid principles are a double-edge sword. Lots of abstractions can make a codebase harder to understand for a new dev. So I try to be pragmatic and allow to deviate from SOLID when necessary.
Even better for DIP is to take a services approach and maintain the logic out of component scope so you don't need to do the useCallback dance as you have stable function references. Which also solves other issues and promotes better application design.
Great explanation.
The code examples in this video should be taken as just that...examples. They're effective at getting the concepts across. They themselves shouldn't be taken as rules. For example, passing all props of native element from a custom input component (there is good reason to restrict props in many cases, that's why it's called abstraction).
these are they very first things a new software engineer must learn...frameworks, technologies, languages etc come afterwards....SOLID, DRY, reusability, testability, extensibility, ease to read/understand code, these principles must be the guiding light for new software engineers...
Great to see SOLID principle in React, as reading SOLID in the form of back-end coding languages kinda bore me a lot tbh :D
Nice one bro....
easy explanation with code. Like that one
👍
The best React SOLID tutorial ever! Awesome!
those are really awsome videos. I just learned java from ground up and to see these principles also applied to react makes so much more sense now!
some constructive criticism for you videos: when you hav a video that has a lot of text like code windows and you put some quotes" like showing a sentence that summarizes the principle you are talking about, it is really hard to read. may put a box or something behind that text you overlay on top :)
Woo! Awesome explanation about S.O.L.I.D principles. I really enjoyed watching the video and learn a lot. Keep it up man.
This might not be the OOP but it's still great to know this method to have a nice and clean code using React. Thanks bro for sharing!
OOP 🙄
Thank you for a great video!
Great video. On the example shown for the Interface Segreggation Principle you could say that doing that (imageUrl prop instead of product) also enforces the SRP because now the Image component doesn't have the responsibility of knowing the shape of a product object and what its imageUrl attribute is called. After the change now it doesn't even know what a product is, and that's beautiful.
Great explanations! As a "native" JS developer I always struggle to understand/translate those OOP principles since I use a lot of functional programming just as React does. This video is pure gold, I will be glad if you make some others videos about things such as design patterns. Thank you!
I think it's important to point out (especially for new developers) that "responsibility" in SRP is not synonymous with function. It would be easy to confuse a responsibility with a single function. The word "responsibility" in the context refers to the sate of the object. In true SRP fashion, a class, component, module, etc, should only have 1 reason to change. That reason is its responsibility.
A good example of this would be an "Employee" class in an HR application. Let's say that class is responsible for holding employee information, like name, age, start date, department, etc. Changing any of those parameters is still within the realm of the class' responsibility. However, if an employee's performance management was added to that class, then that would be a violation of SRP because the class is now responsible for the basic employee information as well as handling the employee performance. The SRP method would be to create two separate classes, EmployeeData and EmployeePerformance.
I like the idea of trying to apply OOP principles to functional programming, even though obviously some of these are subjective interpretation of the original OOP principles.
Great~
What is the theme of vscode?
thanks,
very clear
Any reason you don’t destructure your props in the component definition? your approach prevents linters from finding unused props (which means there’s a bug usually)
I do the same, i always make an interface for the props, then do props:Props, and deconstruct them on the first line of the function. My linter catches these bugs.
Really helpful. Thanks
Thanks man for a great video
Thank you, sir!
Great explanation! 👏
Thank you for this piece. I learned a lot.👏
I really enjoyed these interactive explanations! Thanks for the video!
Awesome thanks for sharing
Wow, this is really awesome. Thank you
I find typescript reduces the ability to read code, increasing cognitive load. Our team stripped Typescript from most of our projects and are very happy we did that
man, i fking hate TS , your team used react js without TS and could go along well ? or every app today is required to have it ?
im glad frontend is finally using clear software engineering principles, but its definitely not react-specific
your useRateFilter hook is purely an alias over useState() since handleRating is identical to setFilterRate. So beside the name clarity benefits are there any principle that I miss behind this ?
You are the best!) Awesome video!👍
Great one , Thanks man
Thank you for this video. It's good and rare to see someone explaining SOLID principles in the React context.
I want to make one addition below.
For the Liskov Substitution Principle, we should also make the current Tailwind classes overridable by using an npm package like tailwind-merge and combining classes that come from parent to the already existing classes in child component by making each individual class overridable. Otherwise when className prop is inside the restProps that are spreaded, a single class like "p-1" passed to the parent would reset all the classes the child component has. Also, instead of passing creating an `isLarge`, you can just pass the tailwind class `text-3xl` to the parent. Otherwise, each time when the designer wants to add a search input, small, big, medium, extra large, etc. you would have to define a new prop which breaks the open closed principle.
hey , im an amateur in react , can you explain why are you using interfaces for the props to create an object instead of directly using the props ? (e.g in 13:07 )
each parameter of a react functional component (the functions you create to represent your components) is wrapped in an object. you cannot, for example, pass a raw string/number/array to a component. you always have to pass the prop name and then the prop value (just like an object). with that in mind, when you use an interface, you are not creating an object, but annotating the type of each prop/key of that objet.
excellent video, Thanks for sharing!
great video
thx
9:20 what if the icon for a specific button must change, now we have to go through the whole source code to change the icon everywhere, any ways to avoid that?
thank you so much 👋👏
may I know the color theme you are using for VS code ? it's look a bit intuitive
starts at 2:25
I did not get why you associated LSP with the search bar - in your example no inheritance was used, or am I mistaken?
Thank you for your awesome video.
“OOP is embarrassing“ by Brian Will is a great video to watch after this one.
Thanks!
Nice explanation in the Reactive world. thanks
Thanks a lot for this video!!!
At 5:44 you're suggesting whenever you use multiple hooks in a component like useState and useEffect, you then immediately think that you should create a separate custom hook. In my opinion that's not what hooks are for, they should encapsulate project-wide reusable logic. And as I saw, you're only using that custom hook in one component to get the list of products so that logic could stay inside that component. What do you think?
I felt the same
Disagree. Its much cleaner to extract it out into a custom hook. Next you'll be telling me that unless a piece of code is used elsewhere it's not worth extracting into its own function.
@@ChillAutos That's not what hooks are for. They are there to encapsulate project-wide reusable logic.
@@archrodney that's a silly assumption to make. A hook is just a function that contains react state and has a couple additional rules around it because of said state. Are you telling me you don't ever extract code into a function if you don't use it anywhere else? It's about readability and maintainability. It's far better to extract complex hook logic into a custom hook you can test than just make a monstrosity of a component with 5-6 hooks for different stuff all weaved into each other. Perfect way to write tech debt from day 0.
@@ChillAutos Of course you can (ab)use hooks for whatever things you'd like but that's like using a element to create a layout. It's certainly possible and many people do that but that's not what it's for. So cleaning up shouldn't be your main use case for custom hooks either...
If you do not export props type from a component, what good is giving a name as "IComponentProps" instead a short one as "Props" ?
Should we create a custom hook in case we need to use fetch data only in one component? Just want to ensure cause my friend dev said it’s not necessary
it was good, thank you for the video
Which vscode theme are you using in your code editor?
Carry on dude, rich content 👏👏👏
Thank you 🖤
Dude the "S" explanation is on point, I can't stress enough how important custom hooks are for maintainability in react.