Building Chrome Extensions with React — Complete Developer Guide (2025)

36 min read

Building Chrome Extensions with React — Complete Developer Guide (2025)

Building Chrome Extensions with React — Complete Developer Guide (2025)

React has become the go-to framework for building modern Chrome extensions. Its component-based architecture, efficient rendering, and vast ecosystem make it an ideal choice for creating polished, maintainable extension UIs. This guide walks you through building production-ready Chrome extensions with React in 2025, from project scaffolding to publishing.


Why Use React for Chrome Extensions

Building Chrome extensions with vanilla JavaScript works, but React transforms the development experience in several critical ways.

Component-Based Architecture

React’s component model maps perfectly to extension development. Your popup, options page, and content script UI can all be built from reusable components. This consistency means developers can work across different parts of the extension without learning different patterns.

// A simple React component for your popup
function SettingsPanel({ settings, onUpdate }) {
  return (
    <div className="settings-panel">
      <h2>Extension Settings</h2>
      <Toggle
        label="Enable notifications"
        checked={settings.notifications}
        onChange={(value) => onUpdate({ notifications: value })}
      />
      <Select
        label="Theme"
        options={['light', 'dark', 'system']}
        value={settings.theme}
        onChange={(value) => onUpdate({ theme: value })}
      />
    </div>
  );
}

State Management

React’s state management patterns (useState, useReducer, Context) solve real problems in extensions. Between service worker restarts and communication across isolated contexts, having predictable state handling is invaluable.

Rich Ecosystem

Need a date picker? A form library? Data visualization? The React ecosystem has battle-tested solutions for everything. Extensions like Tab Suspender Pro leverage React to deliver complex feature sets with polished UIs.

Developer Experience

Hot module replacement, TypeScript support, and modern tooling make development enjoyable. You get immediate feedback as you build, without constantly reloading your extension in Chrome.


Advanced React Patterns for Extensions

Custom Hooks for Chrome APIs

Create reusable hooks for Chrome extension APIs:

// hooks/useChromeStorage.ts
import { useState, useEffect, useCallback } from 'react';

export function useChromeStorage<T>(key: string, initialValue: T) {
  const [storedValue, setStoredValue] = useState<T>(initialValue);
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    // Load initial value from storage
    chrome.storage.local.get(key).then((result) => {
      if (result[key] !== undefined) {
        setStoredValue(result[key]);
      }
      setIsLoading(false);
    });
  }, [key]);

  const setValue = useCallback(async (value: T | ((val: T) => T)) => {
    try {
      const valueToStore = value instanceof Function ? value(storedValue) : value;
      setStoredValue(valueToStore);
      await chrome.storage.local.set({ [key]: valueToStore });
    } catch (error) {
      console.error('Error saving to storage:', error);
    }
  }, [key, storedValue]);

  return [storedValue, setValue, isLoading] as const;
}

// Usage in component
function SettingsPanel() {
  const [theme, setTheme, isLoading] = useChromeStorage('theme', 'light');
  const [notifications, setNotifications] = useChromeStorage('notifications', true);

  if (isLoading) return <div>Loading...</div>;

  return (
    <div>
      <select value={theme} onChange={(e) => setTheme(e.target.value)}>
        <option value="light">Light</option>
        <option value="dark">Dark</option>
      </select>
      <label>
        <input 
          type="checkbox" 
          checked={notifications}
          onChange={(e) => setNotifications(e.target.checked)}
        />
        Enable notifications
      </label>
    </div>
  );
}

Message Passing with React Query

Combine React Query with Chrome messaging for efficient data fetching:

// hooks/useExtensionData.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

async function fetchFromServiceWorker<T>(action: string, payload?: any): Promise<T> {
  return new Promise((resolve, reject) => {
    chrome.runtime.sendMessage({ action, payload }, (response) => {
      if (chrome.runtime.lastError) {
        reject(new Error(chrome.runtime.lastError.message));
      } else if (response?.error) {
        reject(new Error(response.error));
      } else {
        resolve(response.data);
      }
    });
  });
}

export function useUserData() {
  return useQuery({
    queryKey: ['userData'],
    queryFn: () => fetchFromServiceWorker('getUserData'),
    staleTime: 1000 * 60 * 5, // 5 minutes
  });
}

export function useUpdateSettings() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (settings: Settings) => 
      fetchFromServiceWorker('updateSettings', settings),
    onSuccess: () => {
      // Invalidate and refetch
      queryClient.invalidateQueries({ queryKey: ['userData'] });
    },
  });
}

Context for Extension-Wide State

Share state across components:

// context/ExtensionContext.tsx
import { createContext, useContext, ReactNode } from 'react';

interface ExtensionState {
  isEnabled: boolean;
  currentTab: chrome.tabs.Tab | null;
  user: User | null;
}

interface ExtensionContextValue extends ExtensionState {
  toggleExtension: () => void;
  setCurrentTab: (tab: chrome.tabs.Tab) => void;
}

const ExtensionContext = createContext<ExtensionContextValue | null>(null);

export function ExtensionProvider({ children }: { children: ReactNode }) {
  const [state, setState] = useState<ExtensionState>({
    isEnabled: true,
    currentTab: null,
    user: null,
  });

  useEffect(() => {
    // Listen for messages from service worker
    const listener = (message: Message) => {
      if (message.type === 'TAB_UPDATED') {
        setState(s => ({ ...s, currentTab: message.tab }));
      }
    };
    chrome.runtime.onMessage.addListener(listener);
    return () => chrome.runtime.onMessage.removeListener(listener);
  }, []);

  const toggleExtension = () => setState(s => ({ ...s, isEnabled: !s.isEnabled }));
  const setCurrentTab = (tab: chrome.tabs.Tab) => setState(s => ({ ...s, currentTab: tab }));

  return (
    <ExtensionContext.Provider value={{ ...state, toggleExtension, setCurrentTab }}>
      {children}
    </ExtensionContext.Provider>
  );
}

export function useExtension() {
  const context = useContext(ExtensionContext);
  if (!context) throw new Error('useExtension must be used within ExtensionProvider');
  return context;
}

Managing Content Script Communication

Handle communication with content scripts:

// hooks/useContentScript.ts
import { useEffect, useState, useCallback } from 'react';

export function useContentScript(tabId: number | undefined) {
  const [isReady, setIsReady] = useState(false);
  const [data, setData] = useState<any>(null);

  useEffect(() => {
    if (!tabId) return;

    // Check if content script is loaded
    chrome.tabs.sendMessage(tabId, { type: 'PING' }, (response) => {
      if (response?.status === 'ready') {
        setIsReady(true);
      }
    });

    // Listen for messages from content script
    const listener = (message: any, sender: chrome.runtime.MessageSender) => {
      if (sender.tab?.id === tabId) {
        setData(message);
      }
    };
    chrome.runtime.onMessage.addListener(listener);
    return () => chrome.runtime.onMessage.removeListener(listener);
  }, [tabId]);

  const sendToContentScript = useCallback(async (action: string, payload?: any) => {
    if (!tabId || !isReady) {
      throw new Error('Content script not ready');
    }
    return chrome.tabs.sendMessage(tabId, { action, payload });
  }, [tabId, isReady]);

  return { isReady, data, sendToContentScript };
}

Testing React Extensions

Unit Testing with Jest

// components/SettingsPanel.test.tsx
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { SettingsPanel } from './SettingsPanel';

// Mock chrome API
jest.mock('chrome', () => ({
  storage: {
    local: {
      get: jest.fn().mockResolvedValue({ theme: 'light' }),
      set: jest.fn().mockResolvedValue(undefined),
    },
  },
}));

describe('SettingsPanel', () => {
  it('renders settings correctly', () => {
    render(<SettingsPanel 
      settings={{ theme: 'dark', notifications: true }}
      onUpdate={jest.fn()}
    />);
    
    expect(screen.getByDisplayValue('dark')).toBeInTheDocument();
    expect(screen.getByRole('checkbox')).toBeChecked();
  });

  it('calls onUpdate when settings change', async () => {
    const onUpdate = jest.fn();
    render(<SettingsPanel 
      settings={{ theme: 'light', notifications: false }}
      onUpdate={onUpdate}
    />);
    
    fireEvent.change(screen.getByRole('combobox'), { target: { value: 'dark' } });
    
    await waitFor(() => {
      expect(onUpdate).toHaveBeenCalledWith({ theme: 'dark', notifications: false });
    });
  });
});

Integration Testing with Playwright

// tests/extension.spec.ts
import { test, expect } from '@playwright/test';

test('popup interacts with service worker', async ({ page, extension }) => {
  // Load the extension popup
  const popup = await extension.popup();
  
  // Wait for React to render
  await popup.waitForSelector('[data-testid="settings-panel"]');
  
  // Interact with UI
  await popup.click('[data-testid="toggle-theme"]');
  
  // Verify state changed
  await expect(popup.locator('.theme-dark')).toBeVisible();
  
  // Close popup and check background state
  await popup.close();
  
  const background = await extension.background();
  await background.waitForFunction(() => {
    return window.getTheme() === 'dark';
  });
});

Performance Optimization

Code Splitting for Extension Pages

Split your bundle for faster popup loading:

// Lazy load heavy components
const SettingsPanel = lazy(() => import('./components/SettingsPanel'));
const AnalyticsDashboard = lazy(() => import('./components/AnalyticsDashboard'));

function PopupApp() {
  const [currentPage, setCurrentPage] = useState('home');

  return (
    <Suspense fallback={<LoadingSpinner />}>
      {currentPage === 'settings' && <SettingsPanel />}
      {currentPage === 'analytics' && <AnalyticsDashboard />}
      {currentPage === 'home' && <HomePage />}
    </Suspense>
  );
}

Memoization Strategies

Prevent unnecessary re-renders:

import { memo, useCallback, useMemo } from 'react';

// Memoize expensive computations
const processedData = useMemo(() => {
  return expensiveOperation(rawData);
}, [rawData]);

// Memoize callback functions
const handleItemClick = useCallback((id: string) => {
  setSelectedItems(prev => 
    prev.includes(id) 
      ? prev.filter(i => i !== id)
      : [...prev, id]
  );
}, []);

// Memoize entire components
const SettingsSection = memo(function SettingsSection({ 
  title, 
  children 
}: SettingsSectionProps) {
  return (
    <section>
      <h3>{title}</h3>
      {children}
    </section>
  );
});

Optimizing Bundle Size

Configure Vite to remove development code from production:

// vite.config.ts
export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['react', 'react-dom'],
          ui: ['@chakra-ui/react', 'framer-motion'],
        },
      },
    },
  },
  define: {
    'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
  },
});

Project Scaffolding: Vite + React + CRXJS

The fastest way to start is with a modern build toolchain. We’ll use Vite for development, React for UI, and CRXJS for Chrome-specific builds.

Quick Start with the Starter Kit

For a production-ready foundation, use the chrome-extension-react-starter repository. It includes:

  • Vite + React 18 with TypeScript
  • CRXJS for Chrome extension builds
  • Hot module reload configured
  • Proper manifest handling
  • Popup and options page templates
# Clone the starter
git clone https://github.com/theluckystrike/chrome-extension-react-starter.git my-extension
cd my-extension

# Install dependencies
npm install

# Start development
npm run dev

Manual Setup

To understand the full setup, let’s build it ourselves.

First, create the project and install dependencies:

npm create vite@latest my-extension -- --template react-ts
cd my-extension
npm install
npm install -D @crxjs/vite-plugin chrome-extension-manifest-v3

Configure Vite for Chrome extension development:

// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import crx from '@crxjs/vite-plugin';
import manifest from './manifest.json';

export default defineConfig({
  plugins: [
    react(),
    crx({ manifest })
  ],
  build: {
    outDir: 'dist',
    emptyOutDir: true
  }
});

Set up your Manifest V3 configuration:

// manifest.json
{
  "manifest_version": 3,
  "name": "My React Extension",
  "version": "1.0.0",
  "description": "A Chrome extension built with React",
  "permissions": ["storage", "activeTab"],
  "action": {
    "default_popup": "index.html",
    "default_icon": {
      "16": "icons/icon16.png",
      "48": "icons/icon48.png",
      "128": "icons/icon128.png"
    }
  },
  "background": {
    "service_worker": "background.js"
  },
  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["content.js"]
    }
  ]
}

Update your entry point:

// main.tsx - Entry point for popup
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import './index.css';

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

Run npm run dev, open Chrome at chrome://extensions/, enable Developer Mode, click “Load unpacked”, and select your dist folder. Your React extension is now running.


The popup is your extension’s command center. With React, you can build sophisticated interfaces that rival native applications.

Basic Popup Structure

// App.tsx
import { useState, useEffect } from 'react';
import { useStorage } from './hooks/useStorage';
import SettingsPanel from './components/SettingsPanel';
import StatusCard from './components/StatusCard';

export default function App() {
  const [settings, setSettings] = useStorage('settings', {
    theme: 'light',
    notifications: true,
    autoSuspend: false
  });

  const [activeTab, setActiveTab] = useState<TabInfo | null>(null);

  useEffect(() => {
    chrome.tabs.query({ active: true, currentWindow: true })
      .then(([tab]) => setActiveTab(tab));
  }, []);

  return (
    <div className={`popup ${settings.theme}`}>
      <header>
        <h1>Extension Name</h1>
        <StatusCard tab={activeTab} />
      </header>
      <main>
        <SettingsPanel
          settings={settings}
          onUpdate={setSettings}
        />
      </main>
    </div>
  );
}

Styling for Popups

Extension popups have a fixed maximum size. Style appropriately:

/* index.css */
.popup {
  width: 360px;
  min-height: 400px;
  padding: 16px;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}

.popup.dark {
  background: #1a1a1a;
  color: #ffffff;
}

header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 16px;
  padding-bottom: 12px;
  border-bottom: 1px solid #e0e0e0;
}

Content Scripts with React

Injecting React into web pages requires a different approach than the popup. Content scripts run in an isolated world, but you can mount React components to specific DOM nodes.

Creating a Content Script Component

// components/PageOverlay.tsx
import { useState, useEffect } from 'react';

interface PageOverlayProps {
  pageUrl: string;
}

export function PageOverlay({ pageUrl }: PageOverlayProps) {
  const [isVisible, setIsVisible] = useState(false);

  return (
    <div className="extension-overlay">
      <button onClick={() => setIsVisible(!isVisible)}>
        Toggle Extension
      </button>
      {isVisible && (
        <div className="overlay-panel">
          <h3>Page Analysis</h3>
          <p>URL: {pageUrl}</p>
        </div>
      )}
    </div>
  );
}

Injecting the Component

// content.tsx - Entry point
import React from 'react';
import ReactDOM from 'react-dom/client';
import { PageOverlay } from './components/PageOverlay';

function init() {
  const container = document.createElement('div');
  container.id = 'extension-root';
  document.body.appendChild(container);

  const root = ReactDOM.createRoot(container);
  root.render(
    <React.StrictMode>
      <PageOverlay pageUrl={window.location.href} />
    </React.StrictMode>
  );
}

if (document.readyState === 'loading') {
  document.addEventListener('DOMContentLoaded', init);
} else {
  init();
}

Configure the content script in your manifest:

{
  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["content.js"],
      "css": ["content.css"],
      "run_at": "document_end"
    }
  ]
}

Options Page with React Router

For complex extensions, you need multiple configuration screens. React Router handles this elegantly.

Setting Up React Router

// options/App.tsx
import { HashRouter, Routes, Route } from 'react-router-dom';
import Layout from './components/Layout';
import GeneralSettings from './pages/GeneralSettings';
import AdvancedSettings from './pages/AdvancedSettings';
import About from './pages/About';

export default function App() {
  return (
    <HashRouter>
      <Layout>
        <Routes>
          <Route path="/" element={<GeneralSettings />} />
          <Route path="/advanced" element={<AdvancedSettings />} />
          <Route path="/about" element={<About />} />
        </Routes>
      </Layout>
    </HashRouter>
  );
}

Register the options page in your manifest:

{
  "options_page": "options.html"
}

Build options-specific entry points in your Vite config:

// vite.config.ts
export default defineConfig({
  build: {
    rollupOptions: {
      input: {
        popup: 'index.html',
        options: 'options.html'
      }
    }
  }
});

State Management with Zustand/Jotai

Modern state libraries simplify extension state handling. Zustand and Jotai both work well with Chrome’s unique architecture.

Zustand for Extension State

// store/extensionStore.ts
import { create } from 'zustand';
import { persist } from 'zustand/middleware';

interface ExtensionState {
  settings: {
    theme: 'light' | 'dark';
    notifications: boolean;
  };
  updateSettings: (settings: Partial<ExtensionState['settings']>) => void;
}

export const useExtensionStore = create<ExtensionState>()(
  persist(
    (set) => ({
      settings: {
        theme: 'light',
        notifications: true,
      },
      updateSettings: (newSettings) =>
        set((state) => ({
          settings: { ...state.settings, ...newSettings }
        })),
    }),
    {
      name: 'extension-storage',
      storage: {
        getItem: async (name) => {
          const result = await chrome.storage.local.get(name);
          return result[name] ?? null;
        },
        setItem: async (name, value) => {
          await chrome.storage.local.set({ [name]: value });
        },
        removeItem: async (name) => {
          await chrome.storage.local.remove(name);
        },
      },
    }
  )
);

Using the Store in Components

// components/SettingsPanel.tsx
import { useExtensionStore } from '../store/extensionStore';

export function SettingsPanel() {
  const { settings, updateSettings } = useExtensionStore();

  return (
    <div>
      <label>
        <input
          type="checkbox"
          checked={settings.notifications}
          onChange={(e) => updateSettings({ notifications: e.target.checked })}
        />
        Enable Notifications
      </label>
    </div>
  );
}

Chrome.Storage React Hooks

Abstracting chrome.storage into React hooks makes data persistence seamless.

// hooks/useChromeStorage.ts
import { useState, useEffect, useCallback } from 'react';

export function useChromeStorage<T>(key: string, initialValue: T) {
  const [value, setValue] = useState<T>(initialValue);
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    chrome.storage.local.get(key).then((result) => {
      if (result[key] !== undefined) {
        setValue(result[key]);
      }
      setIsLoading(false);
    });
  }, [key]);

  const updateValue = useCallback((newValue: T | ((prev: T) => T)) => {
    setValue((prev) => {
      const resolved = typeof newValue === 'function'
        ? (newValue as (prev: T) => T)(prev)
        : newValue;
      chrome.storage.local.set({ [key]: resolved });
      return resolved;
    });
  }, [key]);

  return [value, updateValue, isLoading] as const;
}

Usage in components:

const [settings, setSettings, isLoading] = useChromeStorage('settings', {
  theme: 'light'
});

if (isLoading) return <LoadingSpinner />;

Hot Module Reload Setup

Nothing slows development like constant manual reloads. Configure HMR for instant updates.

CRXJS HMR Configuration

The CRXJS plugin handles most of this automatically when you run npm run dev. However, for content scripts, you’ll need manual setup:

// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import crx from '@crxjs/vite-plugin';

export default defineConfig({
  plugins: [
    react(),
    crx({
      manifest: './manifest.json',
      contentScripts: {
        injectImmediately: true,
      },
      refreshOnChange: true,
    }),
  ],
});

Manual Reload Handler

For the service worker, Chrome doesn’t support HMR directly. Add a reload listener:

// background.ts
chrome.runtime.onInstalled.addListener(() => {
  console.log('Extension installed or updated');
});

// Manual reload trigger
chrome.runtime.onMessage.addListener((message) => {
  if (message.type === 'RELOAD_EXTENSION') {
    chrome.runtime.reload();
  }
});

Production Build and CRX Packaging

When ready to publish, create a proper production build.

Build Configuration

// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import crx from '@crxjs/vite-plugin';
import { resolve } from 'path';

export default defineConfig({
  plugins: [
    react(),
    crx({
      manifest: './manifest.json',
      autoLaunch: false,
    }),
  ],
  build: {
    outDir: 'dist',
    rollupOptions: {
      input: {
        popup: resolve(__dirname, 'index.html'),
        options: resolve(__dirname, 'options.html'),
      },
    },
  },
});

Creating the CRX Package

Run the build command:

npm run build

This generates a dist folder with your extension, ready for manual upload to the Chrome Web Store or distribution via CRX files.

For automated Chrome Web Store uploads, configure the upload process in your CI/CD pipeline using the Chrome Web Store Publish API.


Publishing to Chrome Web Store

The final step is making your extension available to millions of Chrome users.

Preparing for Publication

  1. Create store assets: 1280x800 screenshots, 440x280 promotional tile
  2. Write compelling copy: Clear name, description highlighting key features
  3. Configure pricing: Free or one-time purchase ($0.99 - $9.99 typical)
  4. Privacy policy: Required if you collect any user data

Upload Process

  1. Go to the Chrome Web Store Developer Dashboard
  2. Create a new item and upload your ZIP file (dist folder compressed)
  3. Fill in store listing details
  4. Submit for review

Review times vary from 24 hours to several days. Ensure your extension follows Chrome Web Store policies to avoid rejection.


Real-World Example: Tab Suspender Pro Architecture

Production extensions like Tab Suspender Pro demonstrate React’s power in extensions. Its architecture includes:

  • React for the popup interface with real-time tab statistics
  • Content scripts for page-level suspend controls
  • Service worker handling background tab management
  • Zustand for managing complex state across contexts
  • CRXJS for reliable builds and updates

The extension demonstrates every pattern covered in this guide, from project setup to production deployment.


Next Steps

Now that you have the foundation, explore these resources to deepen your knowledge:


Testing React Components in Extensions

Testing React components in the extension environment requires special considerations.

Unit Testing with Vitest

Set up Vitest for fast, modern testing:

npm install -D vitest @testing-library/react @testing-library/jest-dom
// setupTests.ts
import '@testing-library/jest-dom';

// Component test
import { render, screen, fireEvent } from '@testing-library/react';
import { SettingsPanel } from './SettingsPanel';

describe('SettingsPanel', () => {
  const defaultProps = {
    settings: { notifications: true, theme: 'light' },
    onUpdate: jest.fn()
  };

  it('renders settings correctly', () => {
    render(<SettingsPanel {...defaultProps} />);
    expect(screen.getByText('Extension Settings')).toBeInTheDocument();
  });

  it('calls onUpdate when toggle changes', () => {
    render(<SettingsPanel {...defaultProps} />);
    fireEvent.click(screen.getByRole('checkbox'));
    expect(defaultProps.onUpdate).toHaveBeenCalledWith({
      notifications: false
    });
  });
});

Integration Testing with Playwright

Test the full extension in a real Chrome environment:

// tests/extension.spec.ts
import { test, expect } from '@playwright/test';

test('popup opens and displays settings', async ({ extension }) => {
  const popup = await extension.newPopup();
  
  await popup.goto('popup.html');
  await expect(popup.locator('text=Extension Settings')).toBeVisible();
  
  // Interact with React component
  await popup.click('[data-testid="toggle-notifications"]');
  
  // Verify storage was updated
  const storage = await extension.getStorage();
  expect(storage.settings.notifications).toBe(false);
});

Mocking Chrome APIs

Use Chrome’s stubbed APIs for consistent testing:

// __mocks__/chrome.ts
export const chrome = {
  storage: {
    local: {
      get: jest.fn().mockResolvedValue({ settings: {} }),
      set: jest.fn().mockResolvedValue(undefined)
    }
  },
  runtime: {
    sendMessage: jest.fn(),
    onMessage: {
      addListener: jest.fn()
    }
  }
};

Performance Optimization for React Extensions

Optimize your React extension for fast load times and smooth interactions.

Code Splitting

Split your bundle to load only what’s needed:

// Lazy load heavy components
const HeavyDashboard = React.lazy(() => import('./Dashboard'));

function App() {
  return (
    <Suspense fallback={<LoadingSpinner />}>
      <HeavyDashboard />
    </Suspense>
  );
}

Memoization Strategies

Prevent unnecessary re-renders:

import { useMemo, useCallback, memo } from 'react';

// Memoize expensive computations
const processedData = useMemo(() => {
  return largeDataset.filter(item => item.active)
    .map(item => transformItem(item));
}, [largeDataset]);

// Memoize callback functions
const handleUpdate = useCallback((id: string, value: string) => {
  setData(prev => ({ ...prev, [id]: value }));
}, []);

// Memoize entire components
const SettingsItem = memo(({ label, value, onChange }) => (
  <div className="settings-item">
    <label>{label}</label>
    <input value={value} onChange={e => onChange(e.target.value)} />
  </div>
));

Extension-Specific Optimizations

// Minimize popup render on open
function usePopupState() {
  const [isOpen, setIsOpen] = useState(false);

  useEffect(() => {
    const handleOpen = () => setIsOpen(true);
    const handleClose = () => setIsOpen(false);

    chrome.action.onClicked.addListener(handleOpen);
    return () => {
      chrome.action.onClicked.removeListener(handleOpen);
    };
  }, []);

  return isOpen;
}

Built by theluckystrike at zovo.one

No previous article
No next article