Module Federation - React TypeError: Cannot read properties of null (reading 'useState')

I'm having this error with React version 19.0.0 when I try to load a MFE with Rsbuild and module federation plugin

React TypeError: Cannot read properties of null (reading 'useState')

Solution

There can be many reasons causing this issue. The most common ones are:

  1. react and react-dom have different versions. To fix this, make sure that they have the same version in package.json and package-lock.json
  2. Two instances of react are loaded. This is caused by the microfrontend loading, and can be fixed by updating the Rsbuild module federation settings. You should configure react and react-dom as a shared dependency and set singleton to true to ensure React is only loaded once when the microfrontend is imported.

Here is an example of rsbuild.config.ts file

import { pluginModuleFederation } from '@module-federation/rsbuild-plugin';
import { defineConfig } from '@rsbuild/core';
import { pluginReact } from '@rsbuild/plugin-react';

export default defineConfig({
  plugins: [
    pluginReact(),
    pluginModuleFederation({
      name: 'app_name',
      remotes: {
        //remote config here
      },
      shared: {
        react: {
          singleton: true,
        },
        'react-dom': {
          singleton: true,
        },
      },
    }),
  ],
});

When you enable singleton moode, the shared dependencies between the remote modules and the host application will only be loaded once. If different versions are present, the higher version will be loaded and a warning will be given for the module with the lower version.

This is also documented in rspack module-federation-plugin documentation:

singleton: Ensure that shared modules are only loaded once between different versions, following the singleton pattern. This is necessary for libraries designed to run as singletons, such as React, as it can prevent various issues caused by instantiating multiple library instances.

Alternative #1

I've been working with microfrontends for a while, and this React singleton issue is super common. Sometimes the singleton configuration isn't enough, especially if you have different build tools or module resolution strategies.

You can also try adding strictVersion: true to force exact version matching:

import { pluginModuleFederation } from '@module-federation/rsbuild-plugin';
import { defineConfig } from '@rsbuild/core';
import { pluginReact } from '@rsbuild/plugin-react';

export default defineConfig({
  plugins: [
    pluginReact(),
    pluginModuleFederation({
      name: 'app_name',
      shared: {
        react: {
          singleton: true,
          strictVersion: true,
          requiredVersion: '19.0.0', // Exact version
        },
        'react-dom': {
          singleton: true,
          strictVersion: true,
          requiredVersion: '19.0.0',
        },
      },
    }),
  ],
});

This approach is more aggressive and will fail the build if versions don't match exactly, which can help catch version mismatches early.

Alternative #2

Another approach I've found effective is using external dependencies to completely exclude React from the remote bundle:

import { pluginModuleFederation } from '@module-federation/rsbuild-plugin';
import { defineConfig } from '@rsbuild/core';
import { pluginReact } from '@rsbuild/plugin-react';

export default defineConfig({
  plugins: [
    pluginReact(),
    pluginModuleFederation({
      name: 'app_name',
      exposes: {
        './MyComponent': './src/components/MyComponent',
      },
      shared: {
        react: {
          singleton: true,
          eager: true, // Load immediately
        },
        'react-dom': {
          singleton: true,
          eager: true,
        },
      },
    }),
  ],
  // Exclude React from remote bundle
  externals: {
    react: 'react',
    'react-dom': 'react-dom',
  },
});

This ensures the remote app doesn't bundle its own React and relies entirely on the host's React instance. It's a bit more complex to set up but eliminates the duplicate React issue entirely.

Alternative #3

If you're still having issues, the problem might be import order or bundling timing. You can try using a bootstrap pattern to ensure React is loaded before any remote modules:

// In your host app
import React from 'react';
import ReactDOM from 'react-dom';

// Ensure React is available globally
window.React = React;
window.ReactDOM = ReactDOM;

// Then load your remote modules
import('./remote-app');

Or use a dynamic import with preloading:

// In your host app
const loadRemoteApp = async () => {
  // Preload React
  await import('react');
  await import('react-dom');
  
  // Then load remote
  const remoteApp = await import('remote-app/MyComponent');
  return remoteApp;
};

This approach gives you more control over the loading sequence and can help resolve timing-related issues.

Last modified: February 8, 2025