SOLID Principles in React

Wednesday, October 4, 20234 min read

In the world of software development, there are few concepts as fundamental as the SOLID principles, these five principles, initially introduced by Robert C. Martin (Uncle Bob), provide a framework for writing clean, maintainable, and extensible code, associated with object-oriented programming, it can also be applied effectively in the world of React.

Single Responsibility Principle (SRP)

The Single Responsibility Principle states that a component should have only one reason to change.

In the context of React encourages creating components with clear and focused responsibilities and each component should do one thing.

1import React, { useState, useEffect } from 'react';
2
3interface User {
4 // Define user properties
5}
6
7function useUserData(): User | null {
8 const [user, setUser] = useState<User | null>(null);
9
10 useEffect(() => {
11 // Fetch user data here and update setUser
12 }, []);
13
14 return user;
15}
16
17function UserProfile() {
18 const user = useUserData();
19
20 return <div>{/* Render user data here */}</div>;
21}

In the UserProfile component, we separate data fetching and rendering. The useUserData hook handles data retrieval, while the UserProfile component is responsible for rendering the user's data.

This separation simplifies debugging, testing, and maintenance because each part of the code has a distinct purpose.

Open-Closed Principle (OCP)

The Open-Closed Principle suggests that a component should be open to extension but closed to modification.

In React this principle encourages the creation of reusable and extensible components, functions, or hooks.

1import React, { useState } from 'react';
2
3function useEnhancedButton(initialText: string) {
4 const [text, setText] = useState(initialText);
5
6 const handleClick = () => {
7 // Enhance the click behavior
8 };
9
10 return { text, handleClick };
11}
12
13function Button() {
14 const { text, handleClick } = useEnhancedButton('Click me');
15
16 return (
17 <button onClick={handleClick}>{text}</button>
18 );
19}

The useEnhancedButton hook enhances the functionality of the Button component without changing the component's source code.

This promotes code reusability, making it easier to add new features or behavior to existing components without modifying their core logic.

Liskov Substitution Principle (LSP)

The Liskov Substitution Principle states that objects of a derived class should be able to replace their base class without affecting the program's correctness.

In React can be demonstrated through component composition, it ensures that specialized components can replace more general components without breaking the expected behavior.

1import React from 'react';
2
3function GenericButton({ label }) {
4 return (
5 <button>{label}</button>
6 );
7}
8
9function PrimaryButton({ label }) {
10 return (
11 <button className="primary-button">{label}</button>
12 );
13}
14
15function ParentComponent() {
16 return (
17 <div>
18 <GenericButton label="Generic Button" />
19 <PrimaryButton label="Primary Button" />
20 </div>
21 );
22}

The PrimaryButton component that specializes in rendering primary buttons with specific styling.

The ParentComponent uses both GenericButton and PrimaryButton components interchangeably, so you can replace GenericButton with PrimaryButton without breaking the expected behavior of ParentComponent.

Interface Segregation Principle (ISP)

The Interface Segregation Principle suggests creating focused and composable components, functions, or hooks with precise interfaces (props or arguments).

This encourages designing interfaces that only expose what is necessary for each component's functionality.

1import React from 'react';
2
3interface ComponentAProps {
4 id: string;
5 name: string;
6}
7
8function ComponentA({ id, name }: ComponentAProps) {
9 return (
10 <>
11 <div>{id}</div>
12 <div>{name}</div>
13 </>
14 );
15}
16
17interface ComponentBProps {
18 image: string;
19}
20
21function ComponentB({ image }: ComponentBProps) {
22 return (
23 <div>{image}</div>
24 );
25}

The ComponentA and ComponentB have interfaces tailored to their specific needs, this approach avoids overloading components with unnecessary props and keeps the interfaces clean and easy to understand.

Dependency Inversion Principle (DIP)

The Dependency Inversion Principle promotes depending on abstractions rather than concrete implementations.

This encourages using dependency injection or context to provide dependencies to components, hooks, or functions.

1import React, { useState, useEffect } from 'react';
2
3interface ApiClient {
4 // Define API client methods and properties
5}
6
7function useDataFetching(apiClient: ApiClient) {
8 const [data, setData] = useState<any | null>(null);
9
10 useEffect(() => {
11 // Fetch data using apiClient and update setData
12 }, [apiClient]);
13
14 return data;
15}
16
17function MyComponent() {
18 const apiClient = useApiClient();
19 const data = useDataFetching(apiClient);
20
21 return (
22 <div>
23 {/* Render data */}
24 </div>
25 );
26}

In the MyComponent, the apiClient dependency is injected into the useDataFetching hook, this decouples the component from the specific API implementation, making it more flexible and testable.

You can easily switch out the apiClient without modifying MyComponent.

Conclusion

Following SOLID principles, you will create React applications that are not only efficient and maintainable but also scalable and adaptable to evolving requirements.

Your codebase becomes more modular, making it easier for developers to collaborate, extend, and maintain your application with confidence.

In the end, these principles enable you to craft enduring, top-notch React applications.


Thursday, June 27, 2024

Introduction of the useBem hook for React

Discover the power of the useBem hook to streamline your CSS class management, learn how to apply the BEM methodology to ensure consistent, readable, and maintainable styling across your front-end projects.


Monday, July 24, 2023

Using startTransition in React

React 18 introduced a new hook called `startTransition`, simplifies managing asynchronous UI transitions in your React applications.