Showing posts with label React. Show all posts
Showing posts with label React. Show all posts

August 28, 2025

Building a Reusable React Component Library with TypeScript and Rollup - A Step-by-Step Guide

Thinking of building your own reusable React component library? Whether it’s to keep your projects consistent or to make collaboration with your team easier, you’re in the right place.

In this guide, I’ll walk you through exactly how I created a shareable React component library from setup to publishing, complete with real code examples, clear explanations, and practical tips. Everything you need is right here in one place.

Use Case

Maintaining multiple React projects with variations of the same UI components presented significant challenges for our team. We encountered frequent issues such as inconsistent styling, duplicated bug fixes, and difficulties in propagating enhancements across codebases. This approach led to inefficiencies, unnecessary overhead, and a lack of coherence in user experience.

To address these challenges, we developed a centralizedReusable Component Library, a standardized collection of UI components designed for use across all our React projects. By consolidating our shared components into a single, well-maintained package, we significantly reduced development redundancy and ensured visual and behavioral consistency throughout our applications. Updates or improvements made to the component library are seamlessly integrated wherever the library is used, streamlining maintenance and accelerating development cycles.


1. Set Up Your Project Folder

First, create a new folder for your component library and initialize it:


mkdir my-react-component-library
cd my-react-component-library
npm init -y

With your project folder in place, you have established a solid foundation for the steps ahead.


2. Install Essential Dependencies

Install React, TypeScript, and essential build tools for a robust library setup:


npm install react react-dom
npm install --save-dev typescript @types/react @types/react-dom
npm install --save-dev rollup rollup-plugin-peer-deps-external rollup-plugin-postcss @rollup/plugin-node-resolve @rollup/plugin-commonjs @rollup/plugin-typescript sass

 The right dependencies are now in place, ensuring your project is equipped for modern development and efficient bundling.


3. Organize Your Project Structure

Establish a clear and logical directory structure for your components and outputs:


With your file structure organized, you are primed for scalable code and easy project navigation.

4. Write Your Component

Develop a simple reusable React component as a starting point for your library:


import React from 'react';
import styles from './HelloWorld.module.scss';
type HelloWorldProps = {
  name: string;
};
export const HelloWorld: React.FC<HelloWorldProps> = ({ name }) => (
  <div className={styles.centerScreen}>
    <div className={styles.card}>
      <span className={styles.waveEmoji}></span>
      <div className={styles.textBlock}>
        <span className={styles.helloSmall}>Hello,</span>
        <span className={styles.name}>{name}</span>
      </div>
    </div>
  </div>
);

Having your first component ready sets the stage for further expansion and consistent styling across your library.


5. Set Up TypeScript

Configure TypeScript for optimal type safety and the generation of type declarations:

{
  "compilerOptions": {
    "declaration": true,
    "declarationDir": "dist/types",
    "emitDeclarationOnly": false,
    "jsx": "react",
    "module": "ESNext",
    "moduleResolution": "node",
    "outDir": "dist",
    "rootDir": "src",
    "target": "ES6",
    "strict": true,
    "esModuleInterop": true
  },
  "include": ["src"]
}

TypeScript is now fully configured, bringing type safety and easy downstream integration for consumers.


6. Create an Index Export

Make src/index.ts like this:

export { HelloWorld } from './HelloWorld';

Centralizing your exports prepares your library for seamless adoption in other projects

7. Add a Type Declarations File

Enable TypeScript to recognize SCSS module imports and prevent type errors:

declare module '*.module.scss' {
  const classes: { [key: string]: string };
  export default classes;
}

With declaration files in place, your styling workflow integrates smoothly with TypeScript.


8. Configure Rollup

Set up Rollup for reliable library bundling and versatile output formats:

import peerDepsExternal from "rollup-plugin-peer-deps-external";
import postcss from "rollup-plugin-postcss";
import resolve from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
import typescript from "@rollup/plugin-typescript";

export default {
  input: "src/index.ts",
  output: [
    {
      file: "dist/index.js",
      format: "cjs",
      sourcemap: true,
    },
    {
      file: "dist/index.esm.js",
      format: "esm",
      sourcemap: true,
    },
  ],
  plugins: [
    peerDepsExternal(),
    resolve(),
    commonjs(),
    typescript({ tsconfig: "./tsconfig.json" }),
    postcss({
      modules: true,
      use: ["sass"],
    }),
  ],
  external: ["react", "react-dom"],
};


An optimized bundling process now supports your library's compatibility with a variety of JavaScript environments.

9. Update package.json

Reference all build outputs and dependencies accurately in your package.json.:

{
  "main": "dist/index.js",
  "module": "dist/index.esm.js",
  "types": "dist/types/index.d.ts",
  "files": [
    "dist"
  ],
  "scripts": {
    "build": "rollup -c"
  },
  "peerDependencies": {
    "react": "^17.0.0 || ^18.0.0",
    "react-dom": "^17.0.0 || ^18.0.0"
  }
}

Your package metadata is set, paving the way for effortless installation and use.


10. Build the Package

Trigger Rollup to bundle your components:

npm run build

With a completed build, your library files are now ready for distribution.


11. Publishing to Azure Artifacts npm Registry

a) Set up your Azure Artifacts Feed

Go to Azure DevOps > Artifacts and create (or use) an npm feed.


b) Configure npm for Azure Artifacts

In your project root, create or update a .npmrc file with:

@yourscope:registry=https://pkgs.dev.azure.com/yourorg/_packaging/yourfeed/npm/registry/
always-auth=true

Replace @yourscope, yourorg, and yourfeed with your actual values.

c) Authenticate Locally

Use Azure's instructions for authentication, such as:

npm login --registry=https://pkgs.dev.azure.com/yourorg/_packaging/yourfeed/npm/registry/

In some setups, especially on Windows, you might need to install and run vsts-npm-auth to complete authentication.

d ) Build Your Package

Ensure your package is built and ready to publish (e.g., run npm run build if you have a build step.

e ) Publish Your Package

From the project root, run:

npm publish

You do not need to specify the registry in the publish command if your .npmrc is set correctly. The registry is picked up from .npmrc.

And just like that, your component library is available in your Azure feed for your team or organization to install and use!

If you’d prefer to publish to the public npm registry, follow these steps:


OR 

12. Publishing to NPM

Prerequisites

  • You already built your library (dist/ exists, with all outputs, after running npm run build).
  • You have an npmjs.com account.

a) Log in to npm 

In your terminal, from the root of your project, type:

npm login

Enter your npm username, password, and email when prompted.

b)  Publish 

Publish the package:

npm publish

After publishing to npmjs.com, you’ll want to showcase your package’s availability directly from your npm dashboard.


Instructions:

  1. Go to npmjs.com and log in to your account.

  2. Click on your username (top-right) and select Packages from the dropdown.

  3. Find and click your newly published package.



Seeing your package live in npm’s dashboard is a proud milestone—your code is now out there, ready to make life easier for every developer who needs it!

Once published, your component library is available for installation in any compatible React project.


install the library in any React project:


npm install your-package-name

Output:

Below is an example of what you'll see after successfully publishing your package to npm. This confirmation means your component library can now be installed and used in any of your React projects.

Troubleshooting/Common Tips:

  • Instructions: If the package name + version already exists on npm, bump your version in package.json.
  • Make sure your main, module, and types fields point to valid files in your dist/ directory (you’ve already done this!).
  • Check .npmignore or the "files" section in package.json so only necessary files are published.

Conclusion:

You've now created, bundled, and published your reusable React component library with TypeScript and Rollup.
This new workflow helps you:

  • Speed up development: No more duplicating code between projects.
  • Guarantee consistency: All your apps share the same reliable components.
  • Simplify updates: Bug fixes or enhancements are made once and shared everywhere.
  • Easily distribute privately or publicly: Works with both internal feeds (like Azure Artifacts) and public npm.

Now your custom components are ready to power future projects, speed up development, and ensure consistency across your apps.

Micro‑Frontends & Component-Driven Architecture: Modern Approaches to Scalable React Applications

In today’s fast-paced development world, building scalable and maintainable frontend applications is critical. As React continues to dominate the ecosystem, two powerful concepts can elevate your architecture: Micro-Frontends and Component-Driven Architecture (CDA). These paradigms help developers break down complex UI systems into manageable, reusable, and independently deployable parts - ideal for large teams and enterprise-scale applications.

What Are Micro-Frontends?


Definition

Micro-Frontends extend the idea of microservices to the frontend. Instead of building a monolithic frontend app, you split it into smaller, independent pieces that are owned by separate teams and developed, tested, and deployed independently.

Example Use Case

Imagine an e-commerce platform where the cart, product list, search, and user profile are each handled by different teams. With Micro-Frontends, each team builds and ships their part of the UI as a self-contained app.

Key Benefits

  • Scalability: Different teams can work in parallel.
  • Independence: Teams can choose their own tech stack.
  • Incremental upgrades: Refactor or rewrite parts without affecting the entire system.
  • Faster deployment cycles.

Common Implementation Strategies

  1. Module Federation (Webpack 5): Share and load code from remote sources.
  2. Iframe-based isolation: Not very modern, but sometimes used.
  3. Runtime integration with single-spa or qiankun.

What Is Component-Driven Architecture (CDA)?


Definition

CDA is a design methodology where UIs are built from the bottom-up using independent, reusable components. This aligns closely with tools like Storybook that enable development in isolation.

Example Use Case

In a component-driven app, a <Button />, <Card />, <LoginForm />, etc., are all designed and tested individually. They’re then composed to form complete pages.

Key Benefits

  • Reusability: Components are shareable across projects.
  • Faster development: Build UIs from pre-tested building blocks.
  • Design-system friendly: Ensures consistency across your app.
  • Better testing: Easier to write unit and visual tests.

Tools and Best Practices

  • Storybook: Develop and preview components in isolation.
  • Bit.dev: Share and reuse components across repositories.
  • Atomic Design Principles: Organize components as atoms, molecules, organisms, etc.

Micro-Frontends + CDA = Ultimate Scalability

Combining these two approaches creates a highly modular, flexible, and scalable frontend system.

  • Micro-Frontends provide separation of concerns at the app level.
  • Component-Driven Architecture ensures reusability and consistency within each micro-app.

Example Flow

  • Each Micro-Frontend owns a domain (e.g., “User Management”).
  • Within that domain, components are developed following CDA.
  • A design system or shared library may be used across teams to maintain consistency.

Challenges & Considerations


Micro-Frontends

  • Shared state management across MFEs can be tricky.
  • Initial setup and CI/CD pipeline management are complex.
  • Performance issues may arise if not optimized properly.

Component-Driven

  • Requires discipline and planning around component design.
  • Over-engineering risk - avoid breaking everything into components unnecessarily.

Conclusion

If you’re building modern React apps at scale, Micro-Frontends and Component-Driven Architecture are concepts worth exploring. They help bring agility, modularity, and maintainability to frontend development - especially in large teams or enterprise-grade projects.

As React ecosystems mature, we’ll see even tighter integrations between these approaches and tools like Turborepo, Module Federation, and Design Systems.

July 31, 2025

React Context vs Redux vs Zustand: Making the Right Choice for Scalable Apps

Introduction

Managing state in a React application is one of the most important tasks for frontend developers. Whether you're working on a small project or a large enterprise application, choosing the right state management tool can affect your app’s performance and maintainability.

In this blog, we’ll explore the differences between React Context, Redux, and Zustand, and help you decide when to use each.


What is React Context?

React Context API is a feature built into React that allows you to share data across multiple components without passing it manually through props. It’s great for small-scale apps or static data like:

  • Light or dark theme 
  • Language settings 
  • User login status

But it’s not ideal when your app has lots of changing data or performance-sensitive features. 


What is Redux?

Redux is one of the most well-known state management libraries used in React. It uses a centralized store, with state updated through a strict pattern involving actions and reducers. Redux is a powerful tool for large applications with complex logic and shared data.

Benefits of Redux:

  • Centralized and predictable state
  • Powerful middleware support
  • Developer tools and time-travel debugging

Challenges: It can be complex to learn and introduces extra boilerplate code.


What is Zustand?

Zustand is a lightweight, modern alternative to Redux. It provides a minimal API and great performance with much less code. Zustand is ideal for small to medium applications and for developers who want something quick and clean to manage state.

Why choose Zustand?

  • Simple and minimal setup
  • High performance with fine-grained updates
  • Supports server-side rendering and middleware


 Comparison Table

Feature

React Context

Redux

Zustand

Built-in

Setup Complexity

Simple

Complex

Very Simple

Best For

Small Apps

Large-Scale Apps

Small to Medium Apps

Performance

⚠️ Not optimized

✅ Good

✅ Excellent

Boilerplate

Minimal

High

Minimal

DevTools


Conclusion

Choosing between React Context, Redux, and Zustand depends entirely on your project’s size, complexity, and how often your data changes. There is no “one best option” - just the right one for the right situation.

Start simple. Use React Context for basic needs, Zustand when you want performance with simplicity, and Redux when your app grows in size and requires structured, scalable state management.

July 24, 2025

React Best Practices: A Comprehensive Guide

In this guide, you'll learn the top React best practices that can help you create clean, scalable, and high-performing apps. Whether you're just getting started or looking to improve your skills, this comprehensive guide covers everything from component structure and state management to performance optimization and code organization.

Component Design and Architecture:

Keep Components Small and Focused
The Single Responsibility Principle applies strongly to React components. Each component should have one clear purpose and do it well. Large components become difficult to test, debug, and maintain.
// ❌ Bad: Large component doing too much
function UserDashboard({ userId }) {
  // 200+ lines of code handling user data, notifications, settings...
}

// ✅ Good: Focused components
function UserDashboard({ userId }) {
  return (
    <div>
      <UserProfile userId={userId} />
      <NotificationPanel userId={userId} />
      <UserSettings userId={userId} />
    </div>
  );
}
Use Composition Over Inheritance
React favors composition patterns. Instead of complex inheritance hierarchies, compose components together to build complex UIs.
// ✅ Good: Composition pattern
function Card({ children, title }) {
  return (
    <div className="card">
      <h2>{title}</h2>
      {children}
    </div>
  );
}

function UserCard({ user }) {
  return (
    <Card title="User Profile">
      <img src={user.avatar} alt={user.name} />
      <p>{user.bio}</p>
    </Card>
  );
}

State Management:

Use the Right Tool for State
Not all state needs to be in a global store. Choose the appropriate state management solution based on your needs:
  • Local state (useState): Component-specific data that doesn't need sharing
  • Lifted state: Shared between a few related components
  • Context: App-wide state like themes or user authentication
  • External libraries: Complex state logic (Redux, Zustand, MobX)
// ✅ Good: Local state for component-specific data
function SearchInput({ onSearch }) {
  const [query, setQuery] = useState('');

  const handleSubmit = (e) => {
    e.preventDefault();
    onSearch(query);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input 
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="Search..."
      />
    </form>
  );
}
Minimize State Dependencies
Keep your state flat and avoid deeply nested objects. This makes updates easier and prevents unnecessary re-renders.
// ❌ Bad: Nested state
const [user, setUser] = useState({
  profile: {
    personal: {
      name: '',
      email: ''
    }
  }
});

// ✅ Good: Flat state
const [userName, setUserName] = useState('');
const [userEmail, setUserEmail] = useState('');

Performance Optimization:

Memoization Strategies
Use React's built-in memoization tools wisely, but don't overuse them. Premature optimization can make code harder to read without significant benefits.
// ✅ Good: Memoize expensive calculations
const ExpensiveComponent = memo(function ExpensiveComponent({ data }) {
  const processedData = useMemo(() => {
    return data.map(item => complexCalculation(item));
  }, [data]);

  return <div>{processedData.map(renderItem)}</div>;
});

// ✅ Good: Memoize callback functions passed to children
function ParentComponent({ items }) {
  const handleItemClick = useCallback((id) => {
    // Handle click logic
  }, []);

  return (
    <div>
      {items.map(item => 
        <ChildComponent 
          key={item.id} 
          item={item} 
          onClick={handleItemClick} 
        />
      )}
    </div>
  );
}
Avoid Inline Objects and Functions
Creating objects or functions inline in render methods causes unnecessary re-renders of child components.
// ❌ Bad: Inline objects cause re-renders
function MyComponent() {
  return <ChildComponent style={{ margin: 10 }} />;
}

// ✅ Good: Define objects outside render or use useMemo
const styles = { margin: 10 };

function MyComponent() {
  return <ChildComponent style={styles} />;
}

Custom Hooks:

Extract Reusable Logic
Custom hooks are perfect for sharing stateful logic between components. They keep your components clean and promote code reuse.
// ✅ Good: Custom hook for API calls
function useApi(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        setLoading(true);
        const response = await fetch(url);
        const result = await response.json();
        setData(result);
      } catch (err) {
        setError(err);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, [url]);

  return { data, loading, error };
}

// Usage in component
function UserProfile({ userId }) {
  const { data: user, loading, error } = useApi(`/api/users/${userId}`);

  if (loading) return <Spinner />;
  if (error) return <ErrorMessage error={error} />;

  return <div>{user.name}</div>;
}
Follow Hook Rules
Always follow the Rules of Hooks: only call hooks at the top level of functions and only from React functions or custom hooks.
// ❌ Bad: Conditional hook usage
function MyComponent({ shouldFetch }) {
  if (shouldFetch) {
    const data = useFetch('/api/data'); // Violates rules of hooks
  }
}

// ✅ Good: Hooks at top level
function MyComponent({ shouldFetch }) {
  const data = useFetch(shouldFetch ? '/api/data' : null);
}

Error Handling:

Implement Error Boundaries
Error boundaries catch JavaScript errors in component trees and display fallback UIs instead of crashing the entire application.
class ErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    console.error('Error caught by boundary:', error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children;
  }
}

// Usage
function App() {
  return (
    <ErrorBoundary>
      <UserDashboard />
    </ErrorBoundary>
  );
}
Handle Async Errors Gracefully
Always handle potential errors in async operations and provide meaningful feedback to users.
function DataComponent() {
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetchData()
      .then(setData)
      .catch(err => {
        setError('Failed to load data. Please try again.');
        console.error(err);
      });
  }, []);

  if (error) {
    return <div className="error">{error}</div>;
  }

  return data ? <DataDisplay data={data} /> : <Loading />;
}

Code Organization and Structure:

Use Consistent File Structure
Organize your files in a logical, consistent manner. Group related files together and use clear naming conventions.












Use TypeScript for Better Development Experience
TypeScript provides excellent developer experience with better autocomplete, refactoring support, and catch errors at compile time.
interface User {
  id: string;
  name: string;
  email: string;
}

interface UserProfileProps {
  user: User;
  onEdit: (user: User) => void;
}

function UserProfile({ user, onEdit }: UserProfileProps) {
  return (
    <div>
      <h2>{user.name}</h2>
      <button onClick={() => onEdit(user)}>
        Edit Profile
      </button>
    </div>
  );
}

Accessibility:

Make Your Apps Inclusive
Always consider accessibility when building React components. Use semantic HTML, proper ARIA attributes, and ensure keyboard navigation works.
function Modal({ isOpen, onClose, title, children }) {
  useEffect(() => {
    if (isOpen) {
      document.body.style.overflow = 'hidden';
    }
    return () => {
      document.body.style.overflow = 'unset';
    };
  }, [isOpen]);

  if (!isOpen) return null;

  return (
    <div 
      className="modal-overlay" 
      onClick={onClose}
      role="dialog"
      aria-modal="true"
      aria-labelledby="modal-title"
    >
      <div className="modal-content" onClick={e => e.stopPropagation()}>
        <h2 id="modal-title">{title}</h2>
        <button 
          onClick={onClose}
          aria-label="Close modal"
          className="close-button"
        >
          ×
        </button>
        {children}
      </div>
    </div>
  );
}

Key Takeaways:

Following these React best practices will help you build applications that are:
  • Maintainable: Clean, organized code that's easy to update and extend
  • Performant: Optimized components that render efficiently
  • Accessible: Inclusive applications that work for all users
  • Scalable: Architecture that grows well with your application's needs
Remember, best practices evolve with the React ecosystem. Stay updated with the latest React documentation, follow the community discussions, and always consider the specific needs of your project when applying these guidelines. The goal is to write code that not only works today but remains maintainable and performant as your application grows.

If you have any questions you can reach out our SharePoint Consulting team here.

June 12, 2025

Performance Optimization in React: Real-World Examples

Introduction

React is powerful, but it can suffer performance issues when components re-render unnecessarily or handle more work than needed.

This guide explores 6 common performance bottlenecks in real-world applications — along with practical techniques to address them. Each example includes before-and-after code and clear explanations of what’s happening, why it matters, and how to optimize it effectively.


1. Unnecessary Re-renders in Child Components

Consider a simple Counter component that displays a number. Even if only the input field is updated, the Counter still re-renders - a common inefficiency in many apps.

Before Optimization

function Counter({ count }) {
  console.log("Counter rendered");
  return <h2>Count: {count}</h2>;
}

function App() {
  const [count, setCount] = React.useState(0);
  const [text, setText] = React.useState("");

  return (
    <div>
      <Counter count={count} />
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <input value={text} onChange={(e) => setText(e.target.value)} />
    </div>
  );
}

After Optimization (using react-window)

const Counter = React.memo(function Counter({ count }) {
  console.log('Counter rendered');
  return <h2>Count: {count}</h2>;
});

What's happening?
Every time the App component re-renders - even when updating the text input — the Counter component also re-renders.
Why it's a problem?
React re-renders all child components by default, even if their props haven’t changed. This adds up fast in large trees.
Optimization Approach
Use React.memo to prevent unnecessary re-renders by memoizing the component unless its props actually change.

2. Rendering Huge Lists Without Virtualization

Rendering thousands of DOM nodes can completely freeze your browser. If you ever render a long list — this is critical.

Before Optimization

function App() {
  const items = Array.from({ length: 10000 }, (_, i) => `Item ${i}`);

  return (
    <div style={{ height: '400px', overflowY: 'auto' }}>
      {items.map((item) => (
        <div key={item}>{item}</div>
      ))}
    </div>
  );
}

After Optimization (using react-window)

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

function App() {
  const items = Array.from({ length: 10000 }, (_, i) => `Item ${i}`);

  return (
    <List
      height={400}
      itemCount={items.length}
      itemSize={35}
      width={'100%'}
    >
      {({ index, style }) => (
        <div style={style}>{items[index]}</div>
      )}
    </List>
  );
}

What's happening?
All 10,000 list items are rendered and pushed into the DOM at once.

Why it's a problem?
Rendering thousands of DOM nodes is memory-intensive and causes the browser to lag or freeze.

How to fix it?
Use virtualization (react-window) to render only visible items and reuse DOM nodes during scroll. Smooth performance, minimal overhead.


3. Function Re-created on Every Render

Every render creates a new function instance (e.g., the onClick handler in a button). This can cause unnecessary re-renders of child components, especially when they are memorized.

Before Optimization

function App() {
  const [count, setCount] = React.useState(0);
  return <Button onClick={() => setCount(count + 1)} />;
}

After Optimization

const handleClick = React.useCallback(() => {
  setCount((prev) => prev + 1);
}, []);
return <Button onClick={handleClick} />;

What's happening?
The inline arrow function gets recreated every time the component re-renders, so it has a new reference each time.

Why it's a problem?
If the Button component is memoized, it will still re-render because the onClick prop changes by reference.

How to fix it?
Wrap the function in useCallback to keep the reference stable across renders and avoid unnecessary updates downstream.

4. Tab Content Re-renders on Every Switch

Switching between tabs causes all tab content to unmount and remount, losing state and triggering unnecessary re-renders.

Before Optimization

{activeTab === 'profile' && <Profile />}
{activeTab === 'settings' && <Settings />}

After Optimization

const tabs = useMemo(() => ({
  profile: <Profile />,
  settings: <Settings />,
}), []);
return tabs[activeTab];


What's happening?
Each tab component mounts/unmounts when switching, resetting its internal state and triggering re-renders.

Why it's a problem?
Components lose their internal state, and there's a noticeable delay on tab switches due to remounting.

How to fix it?
Store components in `useMemo` and toggle visibility instead of remounting. This keeps the component’s state and makes switching faster.


5. Uncontrolled Component Switching to Controlled

Switching an input from uncontrolled to controlled (e.g., setting its value from undefined to a real value) leads to a React warning and a forced re-render.

Before Optimization

function Input({ value }) {
  return <input value={value} onChange={() => {}} />;
}

When value is initially undefined, this becomes uncontrolled, and when it gets a value, React throws a warning and forces a re-render

After Optimization

function Input({ value }) {
  return <input value={value ?? ''} onChange={() => {}} />;
}

What's happening?
If value starts as undefined, React treats the input as uncontrolled. When it gets a value later, it switches to controlled — and React throws a warning.
Why it's a problem?
Switching from an uncontrolled to a controlled input in React causes the field to reset and displays a warning.
How to fix it?
Use a fallback value (value ?? '') to keep the input controlled from the beginning, avoiding warning and preserving expected behavior.


6. Large Components Doing Too Much

Rendering too many components at once in a large bundle can delay the first paint, making your app feel slow and unresponsive.

Before Optimization

function Dashboard() {
  return (
    <>
      <HeavyChart />
      <LiveFeed />
      <WeatherWidget />
      <NotificationPanel />
    </>
  );
}

All components are rendered and mounted at once.

After Optimization (Code Splitting)

const HeavyChart = React.lazy(() => import('./HeavyChart'));

function Dashboard() {
  return (
    <Suspense fallback={<Loader />}>
      <HeavyChart />
      {/* Other components can load similarly */}
    </Suspense>
  );
}

What's happening?
All dashboard components are loaded upfront, even if they’re not visible or immediately needed.

Why it's a problem?
It bloats the initial JavaScript bundle, slowing down your app's first render and hurting metrics like LCP.

How to fix it?
Use React.lazy with Suspense to load components only when you need them. This helps the app load faster and feel more responsive.


Final Thoughts

Performance issues in React often creep in silently - small inefficiencies that add up as your app grows. These optimization patterns are widely applicable and can help improve performance and user experience across React applications of all sizes.