Skip to main content Skip to footer

50 Top Tips for Better React App Development

ReactJS remains one of the most popular JavaScript frameworks. Many developers use it in some form to build standalone apps or enhance other applications. For example, a C# developer might use the .NET core backend while using React to create the front end. Regardless of how you implement React, it’s always good to adhere to best practices to create functional products that perform as they should.

This article provides React developers with 50 examples of React concepts. You should always aim to maintain a clean, efficient, and scalable codebase that can handle projects as they grow in complexity. In the end, you should gain some insights into improving the quality of your React applications.

In this blog, we’ll review tips for:

Ready to Get Started with Our JavaScript Products? Check Them Out Today!

Basic React Best Practices

1. Use Capitals for Component Names

Make sure you capitalize the names of your components. This allows JSX to distinguish between HTML elements and custom components. Remember, React views lowercase names like <div> as a native HTML element. Any capitalized names will be handled as custom React components.

// Capitalized function component
function MyButton() {
    return <button>Click me!</button>;
  }
  
  // Capitalized arrow function component
  const MyHeader = () => <h1>Hello World!</h1>;
  
  // Class component with capitalized name
  class MyComponent extends React.Component {
    render() {
      return <div>Welcome!</div>;
    }
  }
  
  // Using the components
  function App() {
    return (
      <div>
        <MyHeader />
        <MyButton />
        <MyComponent />
      </div>
    );
  }
  
  export default App;
  

2. Organize Your React Components

Tools like Bit help streamline development by making it easier to share, manage, and collaborate on components across multiple projects. Bit allows you to treat every component as an independent package. They become easier to maintain and update without unnecessary code duplication.

3. Use Error Boundaries

Error boundaries are special components that catch JavaScript errors in a child component tree. They then log the errors and display a default UI versus the entire application crashing. Error boundaries isolate errors to the components they wrap, which allows the rest of the app to continue functioning.

import React, { Component } from "react";

class ErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }
  
  static getDerivedStateFromError(error) {
    return { hasError: true }; // Update state and show default UI
  }
  
  componentDidCatch(error, info) {
    console.error("Error caught by boundary:", error, info); // error details
  }
  
  render() {
    if (this.state.hasError) {
      return <h2>Something went wrong. Please try again.</h2>; // Default UI
    }
    return this.props.children; // show children
  }
}

export default ErrorBoundary;

4. Organize Your Files and Folders Logically

It’s always good to set up your components, hooks, and assets in a predictable way. This helps you maintain clean code that’s easier to maintain as your application grows.

5. Use CSS Modules to Avoid Global Conflicts

CSS modules are used to scope CSS locally to React components. You avoid issues like global namespace collisions or style leaks.

6. Separate State Business Logic from the UI

Separating your state business logic makes components cleaner and easier to read. If another developer on the team works on your app, they can quickly determine what items are responsible for logic versus rendering. The separation also makes it easier to run unit tests.

// useProduct.js
import { useState, useEffect } from "react";

export function useProduct() {
  const [user, setProduct] = useState(null);
  
  useEffect(() => {
    fetch("/api/user")
      .then((res) => res.json())
      .then((data) => setProduct(data));
  }, []);
  
  return product;
}

// ProductItem.jsx
import { useProduct } from "./useProduct";

function ProductItem() {
  const product = useProduct ();
  
  return product ? <div>{product.name}</div> : <p>Loading...</p>;
}

export default ProductItem;

7. Use Image Compression

Image compression reduces an image’s file size without significantly impacting quality. Your React app loads faster and is more responsive to users. Compressing images also improves performance on mobile networks and reduces data usage for mobile users.

8. Use GZip Compression

GZip compression reduces the size of files sent from a server to a client. You can compress the size of assets, like JavaScript bundles and CSS, before sending them over a network. This results in reduced network latency and faster page loading.

9. Remove Unnecessary Comments

While comments are great for explaining the purpose of components and functions, adding too much detail can cause confusion. Remember to remove lines, like Console.log or debugger, that are put in place during development.

10. Avoid Performing Work Directly in Render

The render method should only depend on props and state. This area should have no side effects or need for heavy computations. Performing work in render can cause unexpected bugs and other performance issues. In this situation, you might implement useMemo for computations or useEffect for side effects.

import { useMemo } from 'react';

function OrderList({ orders }) {
  const filteredOrders = useMemo(() => {
    return orders.filter(order => order.isActive).sort((a, b) => a.name.localeCompare(b.name));
  }, [users]); // Recomputes only when orders changes
  
  return (
    <ul>
      {filteredOrders.map(order => <li key={order.id}>{order.name}</li>)}
    </ul>
  );
}

11. Use Self-Closing Tags

Self-closing tags represent elements without child content. They make your components more compact and easier to read. Using self-closing tags reduces visible code clutter when you have a lot of elements in your app. They also comply with JSX syntax, which requires properly closing all tags.

<>
<img src="logo.png" alt="Logo" />
<input type="text" placeholder="Enter name" />
<br />
<hr />
<meta charSet="UTF-8" />
<link rel="stylesheet" href="styles.css" />
</>

12. Get Rid of Curly Braces When Passing String-Type Props

Eliminating curly braces when passing string-type props cleans up the clutter in your syntax. Other developers also have an easier time reading and understanding your code. Removing curly braces is also more consistent with HTML standards.

<MyComponent title="Hello World" />

13. Hide Elements With No Children Using the CSS :empty Pseudo-Class

Using the :empty pseudo-class to select elements with no children can keep empty elements from taking up layout space. You don’t have to write more conditional logic to hide empty elements. Instead, you can write it once with CSS and automatically handle all empty elements.

CSS:

p:empty {
    display: none;  /* Hide empty <p> tags */
  }
  

Apply CSS:

p:empty {
    display: none;  /* Hide empty <p> tags */
  }
  

14. Keep Component Size Small

Avoid having big files full of components. Instead, split your components between multiple files in a way that makes sense.

Advanced React Development Techniques

15. Use Functional Components Over Class Components

Because functional components use plain JavaScript functions, they’re easier to read and are more concise. Class components use a lot of boilerplate, making it harder to perform the adjustments you need down the line.

Using functional components with React Hooks, like useState and useContext, allows you to handle state and lifecycle components without classes. You enjoy all the benefits of class components with cleaner syntax.

//Class component example
class Welcome extends React.Component {
  render(){
    return <h1>Hi, {this.props.name}</h1>
  }
}

//Example written as Functional Component
const Welcome = ({name}) => <h1>Hi, {name}</h1>;

16. Aim for Reusability

Another significant reason to switch from class components to functional components is that they’re easier to reuse throughout different React applications. You can avoid making them hard to maintain by creating smaller components.

Below is an example of creating a Button that handles icons and has no other responsibilities.  Making it modular allows you to use this code in different apps without doing a lot of reconfiguring.

function Iconbutton({ onClick, iconClass}) {
  return (
    <button onClick={onClick}>
      <i className={iconClass}></i>
    </button>
  );
}

export default IconButton;

17. Perform Type Checking with PropTypes

PropTypes help you catch bugs by validating the types of props being passed to components. They ensure components receive the correct data types. For example, if you build a component that expects a string value but receives a number, it could lead to UI issues or runtime errors.

A PropType warns you when props don’t match the component’s expected type.

import React from 'react';
import PropTypes from 'prop-types';

function UserCard({ name, age, isMember, hobbies }) {
  return (
    <div>
      <h2>{name}</h2>
      <p>Age: {age}</p>
      <p>Status: {isMember ? "Member" : "Guest"}</p>
      <ul>
        {hobbies.map((hobby, index) => <li key={index}>{hobby}</li>)}
      </ul>
    </div>
  );
}

// ✅ PropTypes type checking
UserCard.propTypes = {
  name: PropTypes.string.isRequired,        //

18. Re-render Components Using the useState Hook

The useState hook allows you to reuse stateful logic already implemented in a separate component. Instead of using class-based components to maintain state, useState helps preserve the scope of a re-rendered component after a variable changes.

import React, { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

export default Counter;

19. Place State at Correct Level

Place state next to the nearest common ancestor when you have multiple components that need to share it. This keeps you from having to duplicate state in each component, allowing you to pass down data and functions through props.

20. Employ Composition Over Inheritance

Composition is a technique for combining simple components to create more complex ones. Think of it like putting Lego bricks together to create flexible, reusable UI structures. Composition lets you build independent components versus tight coupling between parent and child components.

function Card({ title, content, footer }) {
    return (
      <div className="card">
        <h2>{title}</h2>
        <p>{content}</p>
        {footer}
      </div>
    );
  }
  
  function App() {
    return (
      <Card
        title="Welcome!"
        content="This is an example of a composable card component."
        footer={<button>Click Me</button>}
      />
    );
  }
  

21. Use Curried Functions to Reuse Logic

Curried functions are a functional programming technique. Instead of taking all arguments at once, curried functions take the first argument and then return another function that takes the following argument, etc. This powerful technique lets you reuse logic in apps requiring the need to pass partially applied functions as callbacks or props.

const add = a => b => a + b;

const addFive = add(10);  // Partially apply with '10'
console.log(addTen(20));  //

React Performance Optimization

22. Utilize Hooks Over Class Lifecycle Methods

One of the most significant reasons to start using hooks is that they make state management and side effects easier to manage. Below is an example of using both to handle a button click, which causes a re-rerender and updates the document title.

import { useState, useEffect } from 'react';

const Counter = () => {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    document.title = `Count: ${count}`;
  }, [count]);
  
  return <button onClick={() => setCount(count + 1)
};

23. Use Virtualization When Rendering Large Data Sets

Rendering a list or table with a lot of data can slow down your React app’s performance. Using virtualization alongside libraries, like react-windows, can help you deal with this problem. You only render currently visible data in the list, which makes it possible to set up lists of any size.

import React from 'react';
import { FixedSizeList as List } from 'react-window';

const Row = ({ index, style}) => (
  <div style={style}>
    📝 Item #{index + 1}
  </div>
);

function VirtualizedList() {
  return (
    <List
      height={400}       // Height of the list viewport
      itemCount={1000}   // Total number of items
      itemSize={35}      // Height of each item
      width={300}        // Width of the list
    >
      {Row}
    </List>
  );
}



import VirtualizedList from './VirtualizedList';

function App() {
  return (
    <div style={{ padding: '20px' }}>
      <h2> Virtualized List Example</h2>
      <VirtualizedList />
    </div>
  );
}

export default App;

24. Use Debouncing to Limit Excessive Function Calls

The debouncing technique is helpful when handling events like keystrokes or scrolling. Triggering a function too often can hinder your application's performance. Invoking a debounced function sets a specified delay. Firing the same event before the delay ends resets the function. Your function only executes after the event stops executing. This reduces lag in the application, improving overall performance.

import React, { useState, useEffect } from 'react';

function DebouncedInput() {
  const [inputValue, setInputValue] = useState('');
  const [debouncedValue, setDebouncedValue] = useState('');
  
  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(inputValue);
    }, 500); // 500ms debounce delay
    
    // Clear the timeout if inputValue changes before 500ms
    return () => clearTimeout(handler);
  }, [inputValue]);
  
  return (
    <div className="p-4 max-w-md mx-auto">
      <input
        type="text"
        value={inputValue}
        onChange={(e) => setInputValue(e.target.value)}
        placeholder="Type something..."
        className="w-full p-2 border rounded"
      />
      <p className="mt-2 text-gray-600">
        Debounced Value: <strong>{debouncedValue}</strong>
      </p>
    </div>
  );
}

export default DebouncedInput;

25. Optimize Performance With useMemo and useCallback

React components re-render after every prop or state change. Using hooks, like useMemo and useCallback, allows you to cache values and functions. That way, the app doesn’t have to perform intensive recalculations or recreations after each render. The useMemo hook caches calculation results and only recomputes after a dependency change, while the useCallback caches function references.

Example without using useMemo:

function ExpensiveComponent({ number}) {
    const expensiveCalcluation = (num) => {
      console.log("Calculating...");
      return num * 2;
    };
    
    const result = expensiveCalculation(number); // every render gets run
    
    return <p>Result: {result}</p>;
  }

Example using useMemo:

import { useMemo } from "react";

function ExpensiveComponent({ number }) {
  const result = useMemo(() => {
    console.log("Calculating...");
    return number * 2; // Large calculation
  }, [number]); // Recaculates when number changes
  
  return <p>Result: {result}</p>;
}

Example without useCallback:

function Parent({ count }) {
    const handleClick = () => console.log("Clicked!");
    
    return <Child onClick={handleClick} />;
  }
  
  const Child = React.memo(({ onClick }) => {
    console.log("Child rendered");
    return <button onClick={onClick}>Click me</button>;
  });
  

Example with useCallback:

import { useCallback } from "react";

function Parent({ count }) {
  const handleClick = useCallback(() => {
    console.log("Clicked!");
  }, []); // Memoized function reference
  
  return <Child onClick={handleClick} />;
}

26. Use Class Names or CSS-in-JS Libraries for Dynamic Styling

Even though they seem like a quick and easy solution, using inline styles can cause performance issues. It’s also hard to maintain them when you want to change the overall look of your React app. Class names make it easier to maintain the look and feel of your React app.

27. Avoid Inline Function Definitions

If you define functions inside render, return, or JSX inline, you may end up with performance issues and unneeded rerendering. Every new function consumes more memory and processing time, which affects the user experience.

28. Implement Dependency Optimization

Review your code and examine how much it relies on internal and external dependencies. Some of them may provide functionality that isn’t necessary. For example, if you’re using a library and only need 10 of the 50 methods available, see if you can remove any unnecessary functions.

Below is an example of optimizing the useEffect and useCallback dependencies:

const getData = useCallback(() => {
    // get logic
  }, []); // Stable reference prevents unnecessary re-renders
  
  useEffect(() => {
    getData();
  }, [getData]);
  

29. Use CDNs

Content Delivery Networks (CDNs) are distributed servers that deliver web content. You can use CDNs in React to serve static assets like CSS files and images. Because CDNs automatically perform caching, you can reduce the need for repeated downloads. Most CDNs provide additional security protections like TLS/SSL encryption and firewall services.

30. Use Fragments Over DOM Nodes

Fragments let you group multiple elements without needing to add extra DOM nodes. You can avoid multiple <div> elements that complicate your DOM. Fewer DOM nodes also help your pages load faster and use less memory. This is helpful when working with large lists and deeply nested components.

function App() {
    return (
      <>
        <h1>Hello World</h1>
        <p>Let's Start Learning React!</p>
      </>
    );
  }
  

31. Store Item IDs from a List Versus an Item

If you need to track selected items, try capturing the ID versus the item itself. This reduces your app’s state size and memory usage. You avoid having to deal with large objects with unnecessary data. IDs are more efficient and lightweight. You also reduce the chances of accidental mutations when working with more complex objects.

function ItemSelector({ items }) {
    const [selectedItemId, setSelectedItemId] = useState(null);
    
    const selectedItem = items.find((item) => item.id === selectedItemId);
    
    return (
      <div>
        {items.map((item) => (
          <button key={item.id} onClick={() => setSelectedItemId(item.id)}>
            Select {item.name}
          </button>
        ))}
        {selectedItem && <p>Selected: {selectedItem.name}</p>}
      </div>
    );
  }
  

32. Generate Random Keys with Stable Keys

The key prop helps identify elements in lists and optimizes rendering. While you can use Math.random for this purpose, that can lead to performance degradation. Math.random generates keys after every render, which causes components to lose their state. Use a stable key list instead to keep your keys consistent. React will only update changed items and preserve component state.

function StableKeyList({ items }) {
    return (
      <ul>
        {items.map(item => (
          <li key={item.id}>{item.name}</li< // Use a stable individual ID
        ))}
      </ul>
    );
  }

React Code Quality and Testing

33. Use Default Props for Safety

Default props add an additional safety layer to your React components by ensuring there are always valid values. Your component can still function using a default value if a prop is missing. This helps you avoid runtime errors like “Undefined is not an object.” This also means you don’t have to write as many conditional checks.

function Greeting({ name = "Guest" }) {
    return <h1>Greetings, {name}!</h1>;
  }
  
  
  <>
  
    <Greeting /> // Renders: Greetings, Guest!
    <Greeting name="Peter" /> // Renders: Greetings, Peter!
</>

34. Track Dangerous URLs

Tracking problematic URLs helps you prevent cross-site scripting (XSS) attacks, where hackers attempt to inject malicious scripts into your code. You can also stop attackers from manipulating URLs to disclose information within query strings or path parameters.

35. Use a Sanitization Library to Render HTML

Using a sanitization library, like DOMPurify, cleans your HTML and removes potentially harmful code before rendering it. Without sanitization, your app may be more vulnerable to attacks like session hijacking and credential theft.

36. Create Tests for All Code

You can catch errors early and minimize mistakes by testing your components. Testing ensures that components function as intended when implemented.

37. Apply the ES6 Spread Function

The ES6 spread operator syntax is used to copy, merge, and pass data in JavaScript. When used with React, you can simplify tasks (like prop passing, copying and updating state, and merging objects and arrays) without mutating data.  

const props = { name: 'Janet', age: 33, state: 'Florida' };
<MyComponent {...props} />

38. Use Security to Enhance HTTP Authentication

React doesn’t automatically handle authentication. It requires secure backend systems to protect data exchanges. You want to prevent hackers from intercepting sensitive information like passwords. Adding additional security protocols keeps your app compliant with data privacy regulations like GDPR or CCPA.

39. Protect Against Faulty Authentication

Without strong authentication mechanisms, your React app could be vulnerable to data breaches and unauthorized access. Strengthening authentication protects against attacks like brute force and token theft. Use secure methods like OAuth2.0 or JSON Web Tokens (JWT) with signing algorithms like HS256.

40.  Add Default XSS Protection with Data Binding

React’s data binding system provides default XSS protection. It automatically escapes content when rendering variables into the DOM. Adding XSS protection protects your app against malicious script injection into forms or URLs. You can also stop attackers from manipulating client-side data or defacing your UI.

Default React:

function App() {
    const userInput = 'img src="xx" onerror="alert(\'XSS Attack Attempt\')" />;
    
    return <div>{userInput}</div>; // Renders as text, NOT as HTML
  }
  

With DomPurify:

import DOMPurify from 'dompurify';

function SafeComponent({ htmlContent }) {
  const sanitizedContent = DOMPurify.sanitize(htmlContent);
  return <div dangerouslySetInnerHTML={{ __html: sanitizedContent }} />;
}

React-Specific Features

41. Use the React Context API for Global State

The Context API lets you manage global state without prop drilling (passing props down through multiple component layers). That means you can pass data directly to nested components without going through intermediate ones.

import { createContext, useState, useContext } from "react";

// Create context
const ThemeContext = createContext();

// provider component
export function ThemeProvider({ children }) {
  const [theme, setTheme] = useState("light");
  
  const toggleTheme = () => {
    setTheme((prev) => (prev === "light" ? "dark" : "light"));
  };
  
  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

// Custom hook
export function useTheme() {
  return useContext(ThemeContext);
}

42. Use Map Functions to Render Arrays Dynamically

The .map() function lets you render UI elements directly from data arrays. It’s an excellent option when you need to display a list of products, users, or other item lists. You also eliminate the need to repeat JSX code for each item and improve the scalability of your React app.

function DessertList() {
    const desserts = ["Chocolate Cake", "Banana Pie", "Vanilla Pudding"];
    
    return (
      <ul>
        {desserts.map((dessert, index) => (
          <li key={index}>{dessert}</li>
        ))}
      </ul>
    );
  }
  

43. Use Redux DevTools to Debug State

Redux is a browser extension that lets you track, inspect, and debug state and actions in a Redux-powered React app. You can see your entire Redux store at any point and inspect nested state structures without using console logging.  The tool also performs tasks like replaying dispatched actions when testing various scenarios and modifying actions to watch real-time state responses.

You’ll first need to install the Chrome or Firefox extension.

const props = { name: 'Janet', age: 33, state: 'Florida' };
<MyComponent {...props} />

Development Workflow

44. Lazy Load Your Components

With lazy loading, you load components only when needed instead of all at once. This improves app performance and provides a better user experience.

45. Use Snippet Components

Snippet components are great when you need to plug in a piece of reusable code that performs a specific isolated functionality with no dependencies. You can build it once and use it within multiple React apps. You can tap into snippet libraries like ES7 React and JS Snippets.

46. Use Conditional Rendering

Conditional rendering lets you display different UI elements under specific conditions. It’s similar to adding if-else logic. Your application can dynamically adjust content based on user actions, data changes, or app state.

function Greeting({ isLoggedIn }) {
    if (isLoggedIn) {
      return <h1>Welcome back!</h1>;
    }
    return <h1>Please sign in.</h1>;
  }
  

47. Use Higher Order Components (HoCs)

Higher-order components are functions that take components and return a more enhanced component. They’re put in place to reuse component logic and separate concerns. You also avoid the need to duplicate code throughout your application.

import React from 'react';

const withAuth = (WrappedComponent) => {
  return ({ isAuthenticated, ...props }) => {
    if (!isAuthenticated) return <p>Log in here</p>;
    return <WrappedComponent {...props} />;
  };
};

function Dashboard() {
  return <h1>Dashboard</h1>;
}

const ProtectedDashboard = withAuth(Dashboard);

function App() {
  return <ProtectedDashboard isAuthenticated={true} />;
}

export default App;

48. Use Parent/Child Component to Manage Multiple Props

Set up parent components that hold state and pass data to smaller child components. Let the parent focus on logic and use child components for rendering. This structure simplifies management by confining data and logic updates to the parent.

Example of parent:

import React, { useState } from 'react';
import UserCard from './UserCard';

function App() {
  const [user, setUser] = useState({
    name: 'Alice LEe',
    age: 55,
    state: 'Colorado',
    email: 'alicelee@email.com',
  });
  
  return <UserCard user={user} />;
}

export default App;

Example of child:

function UserCard({ user }) {
    const { name, age, location, email } = user;
    
    return (
      <div className="p-4 border rounded">
        <h2>{name}</h2>
        <p>{age} years old</p>
        <p>State: {state}</p>
        <p>Email: {email}</p>
      </div>
    );
  }
  
  export default UserCard;
  
  
  

49. Implement Wishful Thinking

Wishful thinking is a programming technique in which you write code as though a component, feature, or function already exists. The goal is to focus on how you wish to use something before worrying about the implementation details. Wishful thinking speeds up prototyping and encourages developers to think in a way that puts usability first.

50. Distinguish Between Initial State and Current State

Your initial state is the starting value where your component mounts, while your current state represents the latest value after performing state updates. Differentiating between the two allows you to reset and reinitialize logic as needed. You can also maintain predictable state updates and improve component reusability.

import { useState } from 'react';

function Form() {
  const intitialFormState = { name: 'Luke Skywalker' };
  const [formData, setFormData] = useState(initialFormState);
  
  const handleChange = (e) => {
    setFormData({ ...formData, name: e.target.value });
  };
  
  const resetForm = () => setFormData(initialFormState);  // Resets to initial state
  
  return (
    <>
      <input value={formData.name} onChange={handleChange} />
      <button onClick={resetForm}>Reset</button>
    </>
  );
}

Ready to Get Started with Our JavaScript Products? Check Them Out Today!

Conclusion

Believe it or not, these tips only scratch the surface of how you can become a better React developer. Keep checking back for more tips and tricks to help improve the performance and reliability of your applications.

Tags:

comments powered by Disqus