Design Converter
Education
Last updated on Mar 1, 2025
•23 mins read
Last updated on Feb 27, 2025
•23 mins read
Why do some React apps feel smooth while others lag?
The way you structure your React components makes a big difference. Poorly organized code can slow down performance, cause unnecessary re-renders, and make debugging a headache.
Following React components best practices helps keep your code clean, readable, and easy to maintain. A well-planned component hierarchy improves separation of concerns and makes debugging simpler. Whether you use functional or class components, writing them the right way leads to better results.
Let’s go over some best practices to help you build high-quality React apps!
When working on a React project, a single React component should focus on one component responsibility. Splitting logic into smaller components makes your React app easier to manage and refactor. Poorly structured React code often leads to unnecessary complexity and difficulties in maintaining component logic.
Here’s a quick example of a poorly structured React component:
1function UserProfile({ user }) { 2 const [isEditing, setIsEditing] = React.useState(false); 3 4 return ( 5 <div> 6 <h2>{user.name}</h2> 7 <p>Email: {user.email}</p> 8 {isEditing ? ( 9 <input type="text" defaultValue={user.name} /> 10 ) : ( 11 <button onClick={() => setIsEditing(true)}>Edit</button> 12 )} 13 </div> 14 ); 15}
While this component works, it combines UI rendering and state management, making it harder to scale. A better approach is to split it into smaller components.
1function UserName({ name }) { 2 return <h2>{name}</h2>; 3} 4 5function UserEmail({ email }) { 6 return <p>Email: {email}</p>; 7} 8 9function EditButton({ onClick }) { 10 return <button onClick={onClick}>Edit</button>; 11} 12 13function UserProfile({ user }) { 14 const [isEditing, setIsEditing] = React.useState(false); 15 16 return ( 17 <div> 18 <UserName name={user.name} /> 19 <UserEmail email={user.email} /> 20 {isEditing ? ( 21 <input type="text" defaultValue={user.name} /> 22 ) : ( 23 <EditButton onClick={() => setIsEditing(true)} /> 24 )} 25 </div> 26 ); 27}
This approach follows best practices by making the component more modular and easier to maintain.
At the heart of any React project, components serve as the building blocks of a React application. A React component is a reusable, self-contained piece of UI that can manage its own state and logic. Components help structure the user interface efficiently, improving code readability and code quality.
In a well-structured component hierarchy, each React component focuses on one component of the application, making it easy to manage and scale. Components can be classified into parent components (which pass data) and child components (which receive and display data).
Example of a Simple React Component
1function WelcomeMessage() { 2 return <h1>Welcome to the React App!</h1>; 3}
Here, WelcomeMessage is a functional component that returns JSX, defining how the UI should look.
Functional components are JavaScript functions that return JSX. Since React introduced React Hooks, these components can now handle state and side effects, making them the preferred choice in modern React development.
Example of a Functional Component with State
1import React, { useState } from "react"; 2 3function Counter() { 4 const [count, setCount] = useState(0); 5 6 return ( 7 <div> 8 <p>Current Count: {count}</p> 9 <button onClick={() => setCount(count + 1)}>Increment</button> 10 </div> 11 ); 12}
Class components were widely used before React Hooks. They extend React.Component and use the render method to return UI elements. Though still supported in older React versions, they are generally avoided in favor of functional components.
Example of a Class Component
1import React, { Component } from "react"; 2 3class Counter extends Component { 4 constructor(props) { 5 super(props); 6 this.state = { count: 0 }; 7 } 8 9 render() { 10 return ( 11 <div> 12 <p>Current Count: {this.state.count}</p> 13 <button onClick={() => this.setState({ count: this.state.count + 1 })}> 14 Increment 15 </button> 16 </div> 17 ); 18 } 19}
• Less Boilerplate Code: No need for constructors or this keyword.
• Better Performance: React Hooks allow efficient state management without class overhead.
• Easier to Read and Test: Function components improve code readability.
• Encourages Smaller Components: Breaking logic into smaller components improves maintainability.
A well-structured React application consists of multiple React components organized in a component hierarchy. Each React component is responsible for rendering a specific section of the user interface.
• Container Components (or Parent Components) manage state and logic.
• Presentational Components (or Child Components) focus on rendering UI.
Example of Parent-Child Component Relationship
1function ParentComponent() { 2 const message = "Hello from Parent!"; 3 return <ChildComponent text={message} />; 4} 5 6function ChildComponent({ text }) { 7 return <p>{text}</p>; 8}
This separation ensures code reusability, making React development more efficient.
Understanding these fundamentals is key to writing maintainable React code that scales well in any React project.
Organizing your React project properly is essential for maintainability and scalability. A well-structured React application follows best practices for file organization, naming conventions, and component size to improve code quality and code readability.
A clean React folder structure makes navigation easier and prevents clutter. Here’s a commonly used structure for a React project:
1/src 2 /components 3 /Button 4 Button.js 5 Button.test.js 6 Button.module.css 7 /Header 8 Header.js 9 Header.module.css 10 /pages 11 /Home 12 Home.js 13 Home.module.css 14 /hooks 15 useFetch.js 16 /context 17 ThemeContext.js 18 /utils 19 helpers.js 20 App.js 21 index.js
✅ Organize React components into their folders with related styles and tests.
✅ Separate reusable components (e.g., buttons, modals) from page-specific components.
✅ Keep hooks (/hooks), utilities (/utils), and contexts (/context) separate.
✅ Store styles in the same folder as components to maintain modularity.
Proper naming conventions improve code readability and make it easier to understand the component hierarchy.
✅ Component names should be in PascalCase (e.g., UserProfile, NavBar).
✅ Files should match the component name (UserProfile.js → <UserProfile />
).
✅ Hooks should be in camelCase and start with use (useFetch.js).
✅ Context files should include Context in their name (ThemeContext.js).
Example of Correct Naming Conventions:
1// Bad 2function userprofile() {} 3 4// Good 5function UserProfile() {}
1// Bad 2function fetchdata() {} 3 4// Good 5function useFetchData() {}
Separating concerns improves code quality and maintainability.
✅ Styles should be in their own .module.css file or a CSS-in-JS solution.
✅ Component logic (such as hooks) should be abstracted into a separate file.
✅ Tests should be in the same folder as the component for easy discovery.
Example of a Well-Structured Component Folder:
1/Button 2 Button.js 3 Button.module.css 4 Button.test.js
Each React component should focus on one component responsibility. Large components increase complexity and reduce code reusability.
Example of a Bad Component (Too Large)
1function UserProfile({ user }) { 2 const [isEditing, setIsEditing] = React.useState(false); 3 4 return ( 5 <div> 6 <h2>{user.name}</h2> 7 <p>{user.email}</p> 8 {isEditing ? ( 9 <input type="text" defaultValue={user.name} /> 10 ) : ( 11 <button onClick={() => setIsEditing(true)}>Edit</button> 12 )} 13 </div> 14 ); 15}
Refactored into Smaller Components:
1function UserName({ name }) { 2 return <h2>{name}</h2>; 3} 4 5function UserEmail({ email }) { 6 return <p>{email}</p>; 7} 8 9function EditButton({ onClick }) { 10 return <button onClick={onClick}>Edit</button>; 11} 12 13function UserProfile({ user }) { 14 const [isEditing, setIsEditing] = React.useState(false); 15 16 return ( 17 <div> 18 <UserName name={user.name} /> 19 <UserEmail email={user.email} /> 20 {isEditing ? ( 21 <input type="text" defaultValue={user.name} /> 22 ) : ( 23 <EditButton onClick={() => setIsEditing(true)} /> 24 )} 25 </div> 26 ); 27}
The Single Responsibility Principle states that a React component should do one thing well.
✅ Split large components into smaller components.
✅ Extract repeated logic into custom hooks.
✅ Keep presentational and container logic separate.
Example: Using Custom Hook to Manage State
Instead of handling API fetching directly inside a component, extract it into a hook:
1function useFetchData(url) { 2 const [data, setData] = React.useState(null); 3 4 React.useEffect(() => { 5 fetch(url) 6 .then((res) => res.json()) 7 .then((data) => setData(data)); 8 }, [url]); 9 10 return data; 11}
Then use it inside a component:
1function UserProfile() { 2 const user = useFetchData("/api/user"); 3 4 return user ? <h2>{user.name}</h2> : <p>Loading...</p>; 5}
Following these best practices for React development ensures a clean React folder structure, improves code quality, and makes your React app easier to maintain.
In modern React development, functional components have largely replaced class components due to their simplicity, improved performance, and better React hooks integration.
✅ Less Boilerplate Code – No need for constructors or the this keyword.
✅ Better Performance – Functional components avoid the overhead of the render method in class components.
✅ Easier to Read and Maintain – Improved code readability and code quality.
✅ Hooks Support – React hooks allow state and lifecycle management in functional components.
Example: Class Component vs. Functional Component
Class Component (Old Approach)
1import React, { Component } from "react"; 2 3class Counter extends Component { 4 constructor(props) { 5 super(props); 6 this.state = { count: 0 }; 7 } 8 9 increment = () => { 10 this.setState({ count: this.state.count + 1 }); 11 }; 12 13 render() { 14 return ( 15 <div> 16 <p>Count: {this.state.count}</p> 17 <button onClick={this.increment}>Increment</button> 18 </div> 19 ); 20 } 21}
Functional Component with Hooks (Modern Approach)
1import React, { useState } from "react"; 2 3function Counter() { 4 const [count, setCount] = useState(0); 5 6 return ( 7 <div> 8 <p>Count: {count}</p> 9 <button onClick={() => setCount(count + 1)}>Increment</button> 10 </div> 11 ); 12}
The functional component is more concise, eliminating the need for this.state and the render method.
React Hooks allow functional components to manage state and side effects. Some commonly used hooks include:
• useState – Manages component state
• useEffect – Handles side effects like API calls
• useContext – Provides global state management
• useRef – Accesses DOM elements without causing re-renders
• useMemo & useCallback – Optimize performance
Example of useState Hook
1function ToggleButton() { 2 const [isOn, setIsOn] = useState(false); 3 4 return ( 5 <button onClick={() => setIsOn(!isOn)}> 6 {isOn ? "ON" : "OFF"} 7 </button> 8 ); 9}
Example of useEffect Hook for Fetching Data
1import React, { useEffect, useState } from "react"; 2 3function FetchData() { 4 const [data, setData] = useState(null); 5 6 useEffect(() => { 7 fetch("https://api.example.com/data") 8 .then((res) => res.json()) 9 .then((data) => setData(data)); 10 }, []); // Empty dependency array ensures it runs once on mount 11 12 return <p>{data ? data.message : "Loading..."}</p>; 13}
✅ Use Hooks Only in Functional Components – Hooks don’t work inside class components.
✅ Call Hooks at the Top Level – Avoid calling hooks inside loops or conditionals.
✅ Keep useEffect Clean – Always clean up side effects to prevent memory leaks.
✅ Use Custom Hooks for Reusability – Extract reusable logic into custom hooks.
Example of a Custom Hook
1function useFetch(url) { 2 const [data, setData] = useState(null); 3 4 useEffect(() => { 5 fetch(url) 6 .then((res) => res.json()) 7 .then((data) => setData(data)); 8 }, [url]); 9 10 return data; 11} 12 13// Usage in a Component 14function UserProfile() { 15 const user = useFetch("/api/user"); 16 17 return user ? <h2>{user.name}</h2> : <p>Loading...</p>; 18}
Excessive re-renders affect React app performance. Here’s how to optimize performance:
✅ Use React.memo to prevent re-renders of unchanged components.
✅ Use useCallback to memoize functions passed as props.
✅ Use useMemo to cache computed values.
Example: Using React.memo to Prevent Re-renders
1const ChildComponent = React.memo(({ count }) => { 2 console.log("Child Rendered"); 3 return <p>Count: {count}</p>; 4}); 5 6function ParentComponent() { 7 const [count, setCount] = useState(0); 8 9 return ( 10 <div> 11 <ChildComponent count={count} /> 12 <button onClick={() => setCount(count + 1)}>Increment</button> 13 </div> 14 ); 15}
Now, the ChildComponent only re-renders when count changes, improving React project performance.
Building scalable React applications requires a strong emphasis on reusable components and modular design. Well-structured React components enhance code quality, reduce duplication, and improve maintainability.
Creating reusable components simplifies React development and accelerates the development process.
✅ Consistency – Ensures a uniform user interface across the React app.
✅ Less Boilerplate Code – Avoids rewriting the same React components multiple times.
✅ Easier Maintenance – Updates affect all instances of the component.
✅ Improved Code Readability – Modular code makes the React project easier to manage.
Example of a Non-Reusable Button Component
1function SubmitButton() { 2 return <button style={{ backgroundColor: "blue", color: "white" }}>Submit</button>; 3}
This button is not reusable since it has a fixed style and text. A better approach is to make it dynamic:
Reusable Button Component
1function Button({ label, onClick, style }) { 2 return <button onClick={onClick} style={style}>{label}</button>; 3} 4 5// Usage 6<Button label="Submit" onClick={() => console.log("Submitted")} style={{ backgroundColor: "blue" }} /> 7<Button label="Cancel" onClick={() => console.log("Canceled")} style={{ backgroundColor: "red" }} />
Now, the Button component can be used in multiple places with different styles and actions.
Follow the Single Responsibility Principle (SRP) – Each React component should focus on one component responsibility.
Use Props Effectively – Allow customization while keeping default behavior.
Keep Components Generic – Avoid hardcoding UI elements.
Encapsulate Reusable Logic in Hooks – Extract common logic into React hooks.
Use Composition Over Inheritance – Prefer composition instead of Higher-Order Components (HOCs) or class inheritance.
Composition is the practice of assembling smaller components to create complex UI structures, making the React project more modular.
Example of Composition:
1function Card({ title, children }) { 2 return ( 3 <div className="card"> 4 <h2>{title}</h2> 5 <div>{children}</div> 6 </div> 7 ); 8} 9 10// Usage 11<Card title="Profile"> 12 <p>Name: John Doe</p> 13 <p>Email: john@example.com</p> 14</Card>
Using composition, the Card component can wrap different types of content, making it reusable.
Passing props through multiple child components (prop drilling) makes the component hierarchy harder to manage.
1function Grandparent() { 2 const user = "John Doe"; 3 return <Parent user={user} />; 4} 5 6function Parent({ user }) { 7 return <Child user={user} />; 8} 9 10function Child({ user }) { 11 return <p>Welcome, {user}</p>; 12}
Instead of prop drilling , Context API allows global state sharing.
1import React, { createContext, useContext } from "react"; 2 3const UserContext = createContext(); 4 5function Grandparent() { 6 return ( 7 <UserContext.Provider value="John Doe"> 8 <Parent /> 9 </UserContext.Provider> 10 ); 11} 12 13function Parent() { 14 return <Child />; 15} 16 17function Child() { 18 const user = useContext(UserContext); 19 return <p>Welcome, {user}</p>; 20}
Now, Child can access the user value without unnecessary prop passing.
An HOC is a function that wraps a React component to add functionality.
1function withLogging(WrappedComponent) { 2 return function (props) { 3 console.log("Component Rendered"); 4 return <WrappedComponent {...props} />; 5 }; 6} 7 8const LoggedButton = withLogging(Button); 9<LoggedButton label="Click Me" />;
While HOCs were popular, React Hooks have made them less necessary.
The render props pattern allows components to share logic using a function as a prop.
1function DataFetcher({ render }) { 2 const [data, setData] = React.useState(null); 3 4 React.useEffect(() => { 5 fetch("/api/data") 6 .then((res) => res.json()) 7 .then((data) => setData(data)); 8 }, []); 9 10 return render(data); 11} 12 13// Usage 14<DataFetcher render={(data) => <p>{data ? data.message : "Loading..."}</p>} />;
Managing state and props efficiently is crucial for building scalable and maintainable React applications. Poor state management can lead to unnecessary re-renders, prop drilling, and difficult debugging. Following best practices ensures better React code quality and a smooth development process.
Props allow data to be passed from a parent component to a child component. However, passing too many props or deeply nested props can make your React app harder to maintain.
✔ Pass only necessary props – Avoid overloading components with unnecessary data.
✔ Use defaultProps and PropTypes – Improve type safety and ensure expected data formats.
✔ Destructure props – Improve code readability.
Example: Passing Props Correctly
1function UserProfile({ name, email }) { 2 return ( 3 <div> 4 <h2>{name}</h2> 5 <p>{email}</p> 6 </div> 7 ); 8} 9 10// Usage 11<UserProfile name="John Doe" email="john@example.com" />;
Lifting state means moving state management to the parent component when multiple child components need access to the same data.
✅ When multiple components rely on the same state.
✅ When a child component needs to modify the state of a parent component.
✅ When avoiding prop duplication across multiple React components.
Example: Lifting State Up
1function ParentComponent() { 2 const [count, setCount] = React.useState(0); 3 4 return ( 5 <div> 6 <CounterDisplay count={count} /> 7 <CounterButton setCount={setCount} /> 8 </div> 9 ); 10} 11 12function CounterDisplay({ count }) { 13 return <p>Count: {count}</p>; 14} 15 16function CounterButton({ setCount }) { 17 return <button onClick={() => setCount((prev) => prev + 1)}>Increment</button>; 18}
A controlled component is managed by React state and updates based on useState().
1function ControlledInput() { 2 const [value, setValue] = React.useState(""); 3 4 return ( 5 <input 6 type="text" 7 value={value} 8 onChange={(e) => setValue(e.target.value)} 9 /> 10 ); 11}
✅ Recommended for form inputs.
✅ Provides better control and validation.
An uncontrolled component uses useRef to directly manipulate the DOM.
1function UncontrolledInput() { 2 const inputRef = React.useRef(); 3 4 return ( 5 <div> 6 <input type="text" ref={inputRef} /> 7 <button onClick={() => alert(inputRef.current.value)}>Show Value</button> 8 </div> 9 ); 10}
✅ Useful for integrating with third-party libraries.
❌ Harder to track changes and validate input.
When props are passed down multiple levels in the component hierarchy, it leads to prop drilling.
Context API allows state management without excessive prop passing.
1import React, { createContext, useContext } from "react"; 2 3const UserContext = createContext(); 4 5function App() { 6 return ( 7 <UserContext.Provider value="John Doe"> 8 <ParentComponent /> 9 </UserContext.Provider> 10 ); 11} 12 13function ParentComponent() { 14 return <ChildComponent />; 15} 16 17function ChildComponent() { 18 const user = useContext(UserContext); 19 return <p>Welcome, {user}</p>; 20}
For large applications, state management libraries like Redux, Zustand, or Jotai help centralize and manage state efficiently.
Example: Zustand for State Management
1import create from "zustand"; 2 3const useStore = create((set) => ({ 4 count: 0, 5 increment: () => set((state) => ({ count: state.count + 1 })), 6})); 7 8function Counter() { 9 const { count, increment } = useStore(); 10 11 return ( 12 <div> 13 <p>Count: {count}</p> 14 <button onClick={increment}>Increment</button> 15 </div> 16 ); 17}
✅ Zustand simplifies state management without boilerplate code.
✅ Unlike Redux, no reducers or actions are needed.
Using defaultProps and PropTypes ensures components receive the correct data types, preventing runtime errors.
Example: Using PropTypes
1import PropTypes from "prop-types"; 2 3function UserProfile({ name, email }) { 4 return ( 5 <div> 6 <h2>{name}</h2> 7 <p>{email}</p> 8 </div> 9 ); 10} 11 12UserProfile.propTypes = { 13 name: PropTypes.string.isRequired, 14 email: PropTypes.string.isRequired, 15};
Example: Using defaultProps
1UserProfile.defaultProps = { 2 name: "Anonymous", 3 email: "Not provided", 4};
✅ Ensures required props are present.
✅ Provides fallback values when props are missing.
Optimizing performance in a React application ensures smooth rendering, reduced re-renders, and improved responsiveness. Poorly optimized React components can slow down the UI, especially in large applications. Let's explore best practices for improving React app performance.
Unnecessary re-renders occur when a component updates even when its props or state haven't changed.
Wraps a functional component to prevent re-renders unless props change.
1import React from "react"; 2 3const MemoizedComponent = React.memo(({ count }) => { 4 console.log("Re-rendered!"); 5 return <p>Count: {count}</p>; 6}); 7 8function ParentComponent() { 9 const [count, setCount] = React.useState(0); 10 11 return ( 12 <div> 13 <MemoizedComponent count={count} /> 14 <button onClick={() => setCount(count + 1)}>Increment</button> 15 </div> 16 ); 17}
Caches computed values to avoid unnecessary recalculations.
1import React, { useState, useMemo } from "react"; 2 3function ExpensiveCalculation({ num }) { 4 const result = useMemo(() => { 5 console.log("Calculating..."); 6 return num * 2; 7 }, [num]); 8 9 return <p>Result: {result}</p>; 10}
useCallback prevents event handlers from being recreated on every render.
1import React, { useState, useCallback } from "react"; 2 3function Button({ onClick }) { 4 return <button onClick={onClick}>Click Me</button>; 5} 6 7function Parent() { 8 const [count, setCount] = useState(0); 9 10 const increment = useCallback(() => setCount((prev) => prev + 1), []); 11 12 return ( 13 <div> 14 <p>Count: {count}</p> 15 <Button onClick={increment} /> 16 </div> 17 ); 18}
✅ Prevents unnecessary re-renders of child components.
Large React applications benefit from code splitting, reducing initial load time.
Dynamically load components only when needed.
1import React, { lazy, Suspense } from "react"; 2 3const LazyComponent = lazy(() => import("./LazyComponent")); 4 5function App() { 6 return ( 7 <Suspense fallback={<p>Loading...</p>}> 8 <LazyComponent /> 9 </Suspense> 10 ); 11}
✅ Improves page speed by loading only the necessary code.
Rendering large lists can cause performance issues. react-window and react-virtualized optimize rendering by displaying only visible items.
1import { FixedSizeList as List } from "react-window"; 2 3function VirtualizedList({ items }) { 4 return ( 5 <List height={200} itemCount={items.length} itemSize={30} width={300}> 6 {({ index, style }) => <div style={style}>{items[index]}</div>} 7 </List> 8 ); 9}
✅ Prevents rendering all list items at once, improving efficiency.
When rendering lists, always use unique key props to avoid reconciliation issues.
1const users = [{ id: 1, name: "John" }, { id: 2, name: "Jane" }]; 2 3function UserList() { 4 return ( 5 <ul> 6 {users.map((user) => ( 7 <li key={user.id}>{user.name}</li> 8 ))} 9 </ul> 10 ); 11}
🚨 Avoid using indexes as keys unless the list is static.
Side effects in a React application include API calls, subscriptions, timers, and DOM manipulations. Managing these efficiently improves React project performance and prevents memory leaks.
useEffect runs side effects inside functional components. It replaces lifecycle methods like componentDidMount, componentDidUpdate, and componentWillUnmount in class components.
Basic useEffect Example
1import React, { useEffect, useState } from "react"; 2 3function FetchData() { 4 const [data, setData] = useState(null); 5 6 useEffect(() => { 7 fetch("https://api.example.com/data") 8 .then((res) => res.json()) 9 .then((data) => setData(data)); 10 }, []); // Runs only once on mount 11 12 return <p>{data ? data.message : "Loading..."}</p>; 13}
✅ Use Dependencies: Provide dependencies to control re-runs ([] for running once, [state] for specific state updates).
✅ Avoid Empty useEffect Loops: Prevent infinite re-renders by managing dependencies properly.
Excessive API calls slow down your React app and increase server load.
✅ Use State Checks Before Fetching Data
✅ Debounce Search Inputs with useCallback
✅ Cache API Responses using libraries like React Query
1useEffect(() => { 2 if (!data) { 3 fetchData(); 4 } 5}, [data]); // Prevents unnecessary API calls
For side effects like event listeners or intervals, use cleanup functions to prevent memory leaks.
1useEffect(() => { 2 const interval = setInterval(() => { 3 console.log("Running..."); 4 }, 1000); 5 6 return () => clearInterval(interval); // Cleanup on unmount 7}, []);
✅ Always return a cleanup function inside useEffect when dealing with subscriptions, timers, or event listeners.
useEffect is not always the best approach for state updates. Consider:
✅ useReducer – When managing complex state logic.
✅ React Query / SWR – For data fetching with built-in caching.
1import { useQuery } from "react-query"; 2 3function FetchData() { 4 const { data, isLoading } = useQuery("userData", () => 5 fetch("/api/user").then((res) => res.json()) 6 ); 7 8 if (isLoading) return <p>Loading...</p>; 9 return <p>{data.name}</p>; 10}
✅ React Query handles caching, background updates, and error handling efficiently.
Maintaining clean React code improves readability, reusability, and scalability in a React project. Following best practices ensures efficient React development and easier debugging.
DRY encourages reducing duplicate code by using reusable components, helper functions, and custom hooks.
Bad Example (Repeated Code in Components)
1function UserCard({ name, email }) { 2 return ( 3 <div> 4 <h2>{name}</h2> 5 <p>Email: {email}</p> 6 </div> 7 ); 8} 9 10function AdminCard({ name, email }) { 11 return ( 12 <div> 13 <h2>{name}</h2> 14 <p>Email: {email}</p> 15 </div> 16 ); 17}
Refactored Using Reusable Component
1function UserInfo({ name, email }) { 2 return ( 3 <div> 4 <h2>{name}</h2> 5 <p>Email: {email}</p> 6 </div> 7 ); 8} 9 10// Usage 11<UserInfo name="John Doe" email="john@example.com" />; 12<UserInfo name="Admin" email="admin@example.com" />;
✅ Eliminates redundant code and improves code reusability.
Keeping UI rendering separate from state management improves React project structure.
✅ Presentational Components – Handle UI only.
✅ Container Components – Manage state and logic.
Example of Separation of Concerns
1function UserCard({ user }) { 2 return <h2>{user.name}</h2>; 3} 4 5function UserContainer() { 6 const [user, setUser] = React.useState({ name: "John Doe" }); 7 8 return <UserCard user={user} />; 9}
✅ Makes components modular and easier to test.
Custom hooks simplify component logic by extracting reusable state and effects.
Example: Custom Hook for Fetching Data
1function useFetch(url) { 2 const [data, setData] = React.useState(null); 3 4 React.useEffect(() => { 5 fetch(url) 6 .then((res) => res.json()) 7 .then((data) => setData(data)); 8 }, [url]); 9 10 return data; 11} 12 13// Usage in Component 14function UserProfile() { 15 const user = useFetch("/api/user"); 16 17 return user ? <p>{user.name}</p> : <p>Loading...</p>; 18}
✅ Improves code readability and reusability.
Overly nested components reduce code readability and make debugging harder.
Bad Example (Too Much Nesting)
1function App() { 2 return ( 3 <div> 4 <section> 5 <article> 6 <div> 7 <p>Deeply nested content</p> 8 </div> 9 </article> 10 </section> 11 </div> 12 ); 13}
Refactored Using Separate Components
1function Content() { 2 return <p>Refactored content</p>; 3} 4 5function App() { 6 return ( 7 <div> 8 <Content /> 9 </div> 10 ); 11}
✅ Reduces complexity and improves maintainability.
Testing ensures React components work correctly, preventing bugs and regressions in a React application. Well-tested components improve reliability, maintainability, and confidence in React development.
✅ Catches bugs early before they reach production.
✅ Ensures components behave as expected under different scenarios.
✅ Improves maintainability by preventing breaking changes.
✅ Encourages modular and reusable components.
Jest is a testing framework, and React Testing Library helps test React components by simulating user interactions.
Example: Testing a Button Component
1// Button.js 2export function Button({ onClick, label }) { 3 return <button onClick={onClick}>{label}</button>; 4}
1// Button.test.js 2import { render, screen, fireEvent } from "@testing-library/react"; 3import { Button } from "./Button"; 4 5test("renders button with correct label", () => { 6 render(<Button label="Click Me" onClick={() => {}} />); 7 expect(screen.getByText("Click Me")).toBeInTheDocument(); 8}); 9 10test("calls onClick when clicked", () => { 11 const handleClick = jest.fn(); 12 render(<Button label="Click" onClick={handleClick} />); 13 14 fireEvent.click(screen.getByText("Click")); 15 expect(handleClick).toHaveBeenCalledTimes(1); 16});
✅ Ensures component renders correctly
✅ Verifies user interactions work properly
Snapshot tests check if a React component renders consistently.
1import { render } from "@testing-library/react"; 2import { Button } from "./Button"; 3 4test("matches snapshot", () => { 5 const { asFragment } = render(<Button label="Click Me" />); 6 expect(asFragment()).toMatchSnapshot(); 7});
If the component’s structure changes unexpectedly, the test will fail, alerting developers to potential issues.
✅ Keep components small and focused – Easier to test individually.
✅ Avoid testing implementation details – Test behavior, not internal logic.
✅ Use mocks for external dependencies – Prevent API calls in tests.
✅ Write meaningful test cases – Cover different user interactions.
Applying React components best practices makes your code easier to manage and scale. Structuring components well, optimizing state and props, and reducing unnecessary re-renders help improve performance. Writing clean, testable code also makes debugging simpler.
Reusable components, React hooks, and techniques like code splitting keep applications fast and flexible. Handling side effects properly with useEffect, useReducer, or React Query improves data management. Testing with Jest and React Testing Library catches issues early.
Improving your React workflow takes time. Stay updated with new features, experiment with different state management tools, and refine your approach. Small changes can make a big difference in building reliable and scalable applications.
Tired of manually designing screens, coding on weekends, and technical debt? Let DhiWise handle it for you!
You can build an e-commerce store, healthcare app, portfolio, blogging website, social media or admin panel right away. Use our library of 40+ pre-built free templates to create your first application using DhiWise.