If you're a React developer, you probably know how exciting and fun it is to build user interfaces. But as projects grow bigger, things can get messy and hard to maintain. That's where React Design Patterns come in to save the day!
In this article, we're going to cover 11 important design patterns that can make your React code:
- cleaner
- more efficient
- easier to understand
Mastering design patterns is the step towards becoming a senior web developer
But before we dive into the list, let's break down what design patterns actually are and why you should care about them.
What is a Design Pattern in Coding?
A design pattern is a tried-and-tested solution to a common coding problem.
A design pattern is a tried-and-tested solution to a common coding problem. Instead of reinventing the wheel every time you write code, you can use a design pattern to solve the issue in a reliable way. Think of it like a blueprint for your code.
These patterns are not code that you copy and paste, but ideas and structures you can use to improve your work. They help developers organize their projects better and avoid common pitfalls.
Think of it like a blueprint for your code.
Why Use Design Patterns in React?
Using design patterns is essential because they:
- Make Your Code Easy to Read: Clear patterns mean other developers (or future you) can understand your code faster.
- Reduce Bugs: Structured code leads to fewer mistakes.
- Boost Efficiency: You don't have to solve the same problems over and over.
- Improve Collaboration: Teams can work more effectively with shared patterns.
- Scale Better: When your app gets bigger, design patterns keep things from getting chaotic.
You can use design patterns as a benchmark for code quality standards
Now that you know why they matter, let’s get into the 12 React design patterns you should know!
11 React Design Patterns
Design Pattern #1: Container and Presentational Components
This pattern helps you separate the logic of your app (containers) from the display (presentational components). It keeps your code organized and makes each part easier to manage.
What Are Container and Presentational Components?
- Container Components handle the logic and data fetching. They do not concern themselves with how things look.
- Presentational Components focus on the UI. They receive data from props and render it.
Purpose
The purpose of this pattern is to separate concerns.
Containers handle logic, while presentational components handle UI.
This makes your code easier to understand, test, and maintain.
Tips
- Keep Presentational Components Dumb: They should only care about displaying data, not where it comes from.
- Reusable UI: Because presentational components are decoupled from logic, you can reuse them in different parts of your app.
Pros and Cons
Pros ✅ |
Cons ❌ |
---|---|
Clear separation of logic and UI |
Can lead to more files and components |
Easier to test (containers and UI separately) |
Might feel like overkill for simple apps |
Promotes reusable UI components |
|
Best For
- Medium to large applications
- Projects with complex data-fetching logic
Code example
Presentational component
It displays data - that's it.
// UserList.jsx
const UserList = ({ users }) => (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
export default UserList;
Container component
It performs a logic - in this case fetching data.
// UserListContainer.jsx
import { useEffect, useState } from 'react';
import UserList from './UserList';
const UserListContainer = () => {
const [users, setUsers] = useState([]);
useEffect(() => {
fetch('/api/users')
.then(res => res.json())
.then(setUsers);
}, []);
return <UserList users={users} />;
};
export default UserListContainer;
Design Pattern #2: Custom hooks
Custom hooks allow you to extract and reuse stateful logic in your React components. They help you avoid repeating the same logic across multiple components by packaging that logic into a reusable function.
Why Use Custom Hooks
When components share the same logic (e.g., fetching data, handling form inputs), custom hooks allow you to abstract this logic and reuse it.
Naming Convention
Custom hooks should always begin with use
, which follows React's built-in hooks convention (like useState
, useEffect
).
Example: useDataFetch()
Purpose
The goal of custom hooks is to make your code DRY (Don't Repeat Yourself) by reusing stateful logic. This keeps your components clean, focused, and easier to understand.
Tips
- Keep It Focused: Custom hooks should solve a specific problem (e.g., data fetching, form handling).
- Return What You Need: Return only the data and functions your component needs.
- Use Other Hooks Inside: Custom hooks can call other React hooks like
useState
,useEffect
, or even other custom hooks.
Pros and Cons
Pros ✅ |
Cons ❌ |
---|---|
Reduces code duplication |
Can make the code harder to follow if overused |
Keeps components clean and focused |
|
Easy to test and reuse |
|
Best For
- Reusable logic that involves state or effects
- Fetching data, authentication and form handling
Code Example
// useFetch.js
import { useState, useEffect } from 'react';
const useFetch = (url) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch(url)
.then((res) => res.json())
.then((data) => {
setData(data);
setLoading(false);
})
.catch((error) => {
setError(error);
setLoading(false);
});
}, [url]);
return { data, loading, error };
};
export default useFetch;
// Component using the custom hook
import useFetch from './useFetch';
const UserList = () => {
const { data: users, loading, error } = useFetch('https://api.example.com/users');
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
};
export default UserList;
Have you noticed that in this code example we also used Design Pattern #1: Container and Presentational Components 😊
When NOT to Use Custom Hooks
- If the logic is very specific to one component and unlikely to be reused.
- If it introduces unnecessary abstraction, making the code harder to understand.
To reuse JSX markup, create a component.
To reuse logic without React hooks, create a utility function
To reuse logic with React hooks, create a custom hook
Design Pattern #3: Compound Components
A compound component in React is a design pattern where a component is composed of several smaller components that work together. The idea is to create a flexible and reusable component system where each subcomponent has its own specific responsibility, but they work together to form a cohesive whole.
It’s like building a set of Lego pieces that are designed to fit together.
Real life example
A good example is the <BlogCard>
component. Its typical children include a title, description, image, and a “Read More” button. Since the blog consists of multiple pages, you might want to display <BlogCard>
differently depending on the context.
For instance, you might exclude the image on a search results page or display the image above the title on another page. One way to achieve this is by using props and conditional rendering.
However, if there are many variations, your code can quickly become clumsy. This is where Compound Components come in handy. 😊
Example Use Cases
- Tabs
- Dropdown Menus
- Accordions
- Blog & Product Card
Purpose
The purpose of the Compound Component pattern is to give users flexibility in composing UI elements while maintaining a shared state and behavior.
Tips
- Keep State in the Parent: The parent component should manage the shared state.
- Use Context for Deep Nesting: If you have many nested components, React Context can simplify passing state.
Pros and Cons
Pros ✅ |
Cons ❌ |
---|---|
Provides flexibility to compose components |
Can be complex for beginners |
Keeps related components encapsulated |
Harder to understand if components are deeply nested |
Best For
- UI patterns like tabs, accordions, dropdowns, and cards
- Components that need shared state between parts
Code Example
// ProductCard.jsx
export default function ProductCard({ children }) {
return (
<>
<div className='product-card'>{children}</div>;
</>
);
}
ProductCard.Title = ({ title }) => {
return <h2 className='product-title'>{title}</h2>;
};
ProductCard.Image = ({ imageSrc }) => {
return <img className='product-image' src={imageSrc} alt='Product' />;
};
ProductCard.Price = ({ price }) => {
return <p className='product-price'>${price}</p>;
};
ProductCard.Title.displayName = 'ProductCard.Title';
ProductCard.Image.displayName = 'ProductCard.Image';
ProductCard.Price.displayName = 'ProductCard.Price';
// App.jsx
import ProductCard from './components/ProductCard';
export default function App() {
return (
<>
<ProductCard>
<ProductCard.Image imageSrc='https://via.placeholder.com/150' />
<ProductCard.Title title='Product Title' />
<ProductCard.Price price='9.99' />
</ProductCard>
</>
);
}
You can layout inner components in any order 🙂
Design Pattern #4: Prop Combination
The Prop Combination pattern allows you to modify the behavior or appearance of a component by passing different combinations of props. Instead of creating multiple versions of a component, you control variations through the props.
This pattern helps you achieve flexibility and customization without cluttering your codebase with many similar components.
Common Use Cases
- Buttons with different styles (e.g., primary, secondary, disabled)
- Cards with optional elements like images, icons, or titles
Default Values: You can set default values for props to avoid unexpected behavior when no props are provided.
Purpose
The purpose of this pattern is to provide a simple way to create variations of a component without duplicating code. This keeps your components clean and easy to maintain.
Tips
- Combine Boolean Props: For simple variations, use boolean props (e.g.,
isPrimary
,isDisabled
). - Avoid Too Many Props: If a component requires too many props to control behavior, consider breaking it into smaller components. Use Design Pattern #3: Compound Components 🙂
- Use Default Props: Set default values to handle missing props gracefully.
Pros and Cons
Pros ✅ |
Cons ❌ |
---|---|
Reduces the need for multiple similar components |
Can lead to "prop explosion" if overused |
Easy to customize behavior and appearance |
Complex combinations may become hard to understand |
Keeps code DRY (Don't Repeat Yourself) |
|
Best For
- Buttons, cards, alerts, and similar components
- Components with multiple configurable states
Code Example
Let's say you're building a Button component that can vary in style, size, and whether it's disabled:
// Button.jsx
const Button = ({ type = 'primary', size = 'medium', disabled = false, children, onClick }) => {
let className = `btn ${type} ${size}`;
if (disabled) className += ' disabled';
return (
<button className={className} onClick={onClick} disabled={disabled}>
{children}
</button>
);
};
// App.jsx
import Button from './components/Button';
const App = () => (
<div>
<Button type="primary" size="large" onClick={() => alert('Primary Button')}>
Primary Button
</Button>
<Button type="secondary" size="small" disabled>
Disabled Secondary Button
</Button>
<Button type="danger" size="medium">
Danger Button
</Button>
</div>
);
Design Pattern #5: Controlled components
Controlled inputs are form elements whose values are controlled by React state. In this pattern, the form input's value is always in sync with the component's state, making React the single source of truth for the input data.
This pattern is often used for input fields, text areas, checkboxes, and select elements.
The value
of the input element is bound to a piece of React state. When the state changes, the input reflects that change.
Controlled vs. Uncontrolled Components:
- Controlled Components have their value controlled by React state.
- Uncontrolled Components rely on the DOM to manage their state (e.g., using
ref
to access values).
Purpose
The purpose of using controlled components is to have full control over form inputs, making the component behavior predictable and consistent. This is especially useful when you need to validate inputs, apply formatting, or submit data dynamically.
Tips
- Use
onChange
Events: Always update state through theonChange
event handler to keep the input value in sync with the state. - Initialize State: Set an initial state value to avoid
undefined
inputs. - Form Validation: Use controlled components to apply real-time validation or formatting.
Pros and Cons
Pros ✅ |
Cons ❌ |
---|---|
Easy to validate and manipulate inputs |
Can require more boilerplate code |
Makes form elements predictable and easier to debug |
May lead to performance issues with very large forms |
Full control over user input |
|
Best For
- Forms with Validation: When you need real-time validation or feedback on input fields.
- Dynamic Forms: When form inputs depend on dynamic data or logic.
- Complex User Inputs: When inputs require transformations, like formatting phone numbers or emails.
Code Example
import { useState } from 'react';
function MyForm() {
const [name, setName] = useState('');
const handleChange = (e) => {
setName(e.target.value);
};
return (
<form>
<input
type="text"
value={name}
onChange={handleChange}
/>
<p>Your name is: {name}</p>
</form>
);
}
Design Pattern #6: Error boundaries
Error Boundaries are React components that catch JavaScript errors in their child component tree during rendering, lifecycle methods, and event handlers. Instead of crashing the entire application, Error Boundaries display a fallback UI to handle errors gracefully.
This pattern is crucial for making React applications more robust and user-friendly.
Purpose
The purpose of Error Boundaries is to prevent an entire application from crashing when a component encounters an error. Instead, they show a user-friendly fallback UI, allowing the rest of the application to remain functional.
Tips
- Wrap Critical Components: Use Error Boundaries around components that are likely to fail (e.g., third-party integrations).
- Logging Errors: Log errors into services like Sentry or LogRocket for debugging.
- Fallback UI: Design a clear fallback UI to inform users that something went wrong.
Pros and Cons
Pros ✅ |
Cons ❌ |
---|---|
Prevents the entire app from crashing |
Cannot catch errors in event handlers or asynchronous code |
Provides a fallback UI for a better user experience |
|
Helps catch and log errors in production |
|
Best For
- Large Applications: Where errors in one component shouldn't crash the entire app.
- Third-Party Integrations: When embedding third-party widgets that may fail unpredictably.
- Complex UI Components: For components with dynamic content or heavy rendering logic.
Code Example
React has a built in way to use Error Boundary design pattern. But it’s a bit outdated (still uses a class component). A better recommendation would to use a dedicated npm library: react-error-boundary
import { ErrorBoundary } from "react-error-boundary";
<ErrorBoundary fallback={<div>Something went wrong</div>}>
<App />
</ErrorBoundary>
Design Pattern #7: Lazy Loading (Code Splitting)
Lazy Loading is a technique where components or parts of your app are loaded only when they are needed. Instead of loading everything at once when the app starts, lazy loading helps split the code into smaller chunks and load them on demand. This improves performance by reducing the initial load time of your application.
How Does It Work in React?
React supports lazy loading through the React.lazy()
function and Suspense
component.
React.lazy()
: This function lets you dynamically import a component.Suspense
: Wraps around a lazily loaded component to show a fallback (like a loading spinner) while waiting for the component to load.
Purpose
The purpose of lazy loading is to optimize the application's performance by reducing the initial bundle size. This leads to faster load times, especially for large applications where not all components are needed immediately.
Tips
- Split Routes: Use lazy loading for routes to load only the components necessary for each page.
- Error Boundaries: Combine lazy loading with Error Boundaries to handle failures in loading components. Design Pattern #6: Error boundaries 🙂
Pros and Cons
Pros ✅ |
Cons ❌ |
---|---|
Reduces initial load time |
Adds slight delays when loading components |
Improves performance for large apps |
Requires handling of loading states and errors |
Loads code on demand, saving bandwidth |
Complexity increases with too many chunks |
Best For
- Large Applications: Apps with many components or pages.
- Single Page Applications (SPA): Where different views are rendered dynamically.
- Non-Critical Components: Components that aren't needed during the initial render, such as modals or heavy widgets.
Code Example
// Profile.jsx
const Profile = () => {
return <h2>This is the Profile component!</h2>;
};
export default Profile;
// App.jsx
import { Suspense, lazy } from 'react';
// Lazy load the Profile component
const Profile = lazy(() => import('./Profile'));
function App() {
return (
<div>
<h1>Welcome to My App</h1>
{/* Suspense provides a fallback UI while the lazy component is loading */}
<Suspense fallback={<div>Loading...</div>}>
<Profile />
</Suspense>
</div>
);
}
export default App;
Design Pattern #8: Higher-Order Component (HOC)
A higher-order component takes in a component as an argument and returns a supercharged component injected with additional data or functionality.
HOCs are often used for logic reuse, such as authentication checks, fetching data, or adding styling.
Signature of an HOC
const EnhancedComponent = withSomething(WrappedComponent);
-
WrappedComponent
: The original component that is being enhanced. -
EnhancedComponent
: The new component returned by the HOC.Naming Convention
HOCs are often named with thewith
prefix, such aswithAuth
,withLogging
, orwithLoading
.
Tips
- Pure Functions: Keep your HOCs pure — they should not modify the original
WrappedComponent
. - Props Forwarding: Always pass down the props to the
WrappedComponent
to ensure it receives everything it needs.
Pros and Cons
Pros ✅ |
Cons ❌ |
---|---|
Promotes code reuse |
Can lead to "wrapper hell" (too many nested HOCs) |
Keeps components clean and focused on their main task |
Harder to debug due to multiple layers of abstraction |
Best For
- Cross-Cutting Concerns: Adding shared logic like authentication, logging, or theming.
- Reusable Enhancements: When multiple components need the same behavior.
- Complex Applications: Apps where common logic needs to be abstracted away for readability.
Higher-Order Component (HOC) is an advanced React pattern
Code Example
Here’s an example of a Higher-Order Component that adds a loading state to a component:
// HOC - withLoading.js
// it returns a functional component
const withLoading = (WrappedComponent) => {
return ({ isLoading, ...props }) => {
if (isLoading) {
return <div>Loading...</div>;
}
return <WrappedComponent {...props} />;
};
};
export default withLoading;
// DataComponent.js
const DataComponent = ({ data }) => {
return <div>Data: {data}</div>;
};
export default DataComponent;
// App.js
import { useState, useEffect } from 'react';
import withLoading from './withLoading';
import DataComponent from './DataComponent';
// supercharching with HOC
const DataComponentWithLoading = withLoading(DataComponent);
const App = () => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
setTimeout(() => {
setData('Here is the data!');
setLoading(false);
}, 2000);
}, []);
return (
<div>
<h1>My App</h1>
<DataComponentWithLoading isLoading={loading} data={data} />
</div>
);
};
export default App;
Design Pattern #9: State Management with Reducers
When the app’s state is more complex instead of using useState
to manage your application's state, you can use reducers.
Reducers allow you to handle state transitions in a more predictable and organized way.
A reducer is simply a function that takes the current state and an action, then returns the new state.
Basics
Reducer Function: A pure function that takes state
and action
as arguments and returns a new state.
const reducer = (state, action) => {
switch (action.type) {
case 'INCREMENT':
return { count: state.count + 1 };
case 'DECREMENT':
return { count: state.count - 1 };
default:
return state;
}
};
Action: An object that describes what kind of state update should happen. Actions usually have a type
field and may include additional data (payload).
Dispatch: A function used to send actions to the reducer, triggering a state update.
Purpose
This pattern is useful when the state logic becomes too complex for useState
. It centralizes state updates, making your code easier to manage, debug, and scale.
Tips
- Keep Reducers Pure: Ensure your reducer function has no side effects (no API calls or asynchronous code).
- Use Constants for Action Types: To avoid typos, define action types as constants.
Pros and Cons
Pros ✅ |
Cons ❌ |
---|---|
Simplifies complex state logic |
Adds boilerplate code (actions, dispatch, etc.) |
Centralizes state updates for easier debugging |
Can be overkill for simple state management |
Makes state transitions predictable |
Requires learning curve for beginners |
Best For
- Complex State Logic: When state transitions depend on multiple conditions.
- Medium to Large Applications: Apps with multiple components sharing state.
Code Example
Here’s an example of state management with useReducer
in a counter app:
import { useReducer } from 'react';
// Step 1: Define the reducer function
const reducer = (state, action) => {
switch (action.type) {
case 'INCREMENT':
return { count: state.count + 1 };
case 'DECREMENT':
return { count: state.count - 1 };
case 'RESET':
return { count: 0 };
default:
return state;
}
};
// Step 2: Define the initial state
const initialState = { count: 0 };
// Step 3: Create the component
const Counter = () => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<h1>Count: {state.count}</h1>
<button onClick={() => dispatch({ type: 'INCREMENT' })}>Increment</button>
<button onClick={() => dispatch({ type: 'DECREMENT' })}>Decrement</button>
<button onClick={() => dispatch({ type: 'RESET' })}>Reset</button>
</div>
);
};
export default Counter;
In modern React development, Redux is the library that uses reducers for state management.
Design Pattern #10: Data management with Providers (Context API)
The provider pattern is very useful for data management as it utilizes the context API to pass data through the application's component tree. This pattern is an effective solution to prop drilling, which has been a common concern in react development.
Context API is the solution to prop drilling
Providers allow you to manage global state in a React application, making it accessible to any component that needs it.
This pattern helps avoid prop drilling (passing props through many layers) by offering a way to "provide" data to a component tree.
Basics
- Context: A React feature that allows you to create and share state globally.
- Provider: A component that supplies data to any component in its child tree.
- Consumer: A component that uses the data provided by the
Provider
. useContext
Hook: A way to access context values without needing aConsumer
.
Purpose
The purpose of this pattern is to simplify data sharing between deeply nested components by creating a global state accessible via a Provider
. It helps keep code clean, readable, and free of unnecessary prop passing.
Tips
- Use Context Wisely: Context is best for global state like themes, authentication, or user settings.
- Combine with Reducers: For complex state logic, combine Context with
useReducer
for more control. - Split Contexts: Instead of one giant context, use multiple smaller contexts for different types of data.
Pros and Cons
Pros ✅ |
Cons ❌ |
---|---|
Reduces prop drilling |
Not ideal for frequently changing data (can cause unnecessary re-renders) |
Centralizes data for easier access |
Performance issues if context value changes often |
Simple to set up for small to medium-sized apps |
|
Best For
- Global State: Sharing themes, authentication status, language settings, etc.
- Avoiding Prop Drilling: When data needs to be passed through multiple component layers.
- Medium Complexity Apps: Apps that need simple global state management.
Code Example
Here’s an example of data management with a ThemeProvider
:
// ThemeContext.jsx
import { createContext, useState } from 'react';
export const ThemeContext = createContext();
export const ThemeProvider = ({ children }) => {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(prevTheme => (prevTheme === 'light' ? 'dark' : 'light'));
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
};
// ThemeToggleButton.jsx
import { useContext } from 'react';
import { ThemeContext } from './ThemeContext';
const ThemeToggleButton = () => {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<button onClick={toggleTheme}>
Switch to {theme === 'light' ? 'dark' : 'light'} mode
</button>
);
};
export default ThemeToggleButton;
// App.js
import { ThemeProvider } from './ThemeContext';
import ThemeToggleButton from './ThemeToggleButton';
const App = () => {
return (
<ThemeProvider>
<div>
<h1>Welcome to the App</h1>
<ThemeToggleButton />
</div>
</ThemeProvider>
);
};
export default App;
In React 19, you can render <Context> as a provider instead of <Context.Provider>
const ThemeContext = createContext('');
function App({children}) {
return (
<ThemeContext value="dark">
{children}
</ThemeContext>
);
}
Design Pattern #11: Portals
Portals allow you to render children into a different part of the DOM tree that exists outside the parent component's hierarchy.
This is useful for rendering elements like modals, tooltips, or overlays that need to be displayed outside the normal DOM flow of the component.
Even though the DOM parent changes, the React component structure stays the same.
Purpose
The purpose of this pattern is to provide a way to render components outside the parent component hierarchy, making it easy to manage certain UI elements that need to break out of the flow, without disrupting the structure of the main React tree.
Tips
- Modals and Overlays: Portals are perfect for rendering modals, tooltips, and other UI elements that need to appear on top of other content.
- Avoid Overuse: While useful, portals should be used only when necessary, as they can complicate the component hierarchy and event propagation.
Pros and Cons
Pros ✅ |
Cons ❌ |
---|---|
Keeps the component tree clean and avoids layout issues |
Can complicate event propagation (e.g., click events may not bubble) |
Best For
- Overlays: Modals, tooltips, or any UI element that needs to appear on top of other content.
- Breaking DOM Flow: When elements need to break out of the standard component hierarchy (e.g., notifications).
Code Example
// Modal.jsx
import { useEffect } from 'react';
import ReactDOM from 'react-dom';
const Modal = ({ isOpen, closeModal, children }) => {
// Prevent body scrolling when modal is open
useEffect(() => {
if (isOpen) {
document.body.style.overflow = 'hidden';
}
return () => {
document.body.style.overflow = 'unset';
};
}, [isOpen]);
if (!isOpen) return null;
return ReactDOM.createPortal(
<>
{/* Overlay */}
<div
style={overlayStyles}
onClick={closeModal}
/>
{/* Modal */}
<div style={modalStyles}>
{children}
<button onClick={closeModal}>Close</button>
</div>
</>,
document.getElementById('modal-root')
);
};
const overlayStyles = {
...
};
const modalStyles = {
...
};
export default Modal;
// App.js
import { useState } from 'react';
import Modal from './Modal';
const App = () => {
const [isModalOpen, setIsModalOpen] = useState(false);
return (
<div>
<h1>React Portals Example</h1>
<button onClick={() => setIsModalOpen(true)}>Open Modal</button>
<Modal isOpen={isModalOpen} closeModal={() => setIsModalOpen(false)}>
<h2>Modal Content</h2>
<p>This is the modal content</p>
</Modal>
</div>
);
};
export default App;
// index.html
<body>
<div id="root"></div>
<div id="modal-root"></div>
</body>
What’s in the end?
Learning and mastering design patterns is a crucial step toward becoming a senior web developer. 🆙
These patterns are not just theoretical; they address real-world challenges like state management, performance optimization, and UI component architecture.
By adopting them in your everyday work, you'll be equipped to solve a variety of development challenges and create applications that are both performant and easy to maintain.
Liked the article? 😊
You can learn more at my personal Javascript blog ➡️ https://jssecrets.com/.
Or you can see my projects and read case studies at my personal website ➡️ https://ilyasseisov.com/.
Happy coding! 😊