module-federation/module-federation-examples

Exposed component cannot be wrapped with Redux provider when remote is consumed as dynamic remotes

Closed this issue · 10 comments

I have two react apps running on the following stack

  • node 20.17.0
  • react 18.3.1
  • webpack 5
  • redux 5.0.1
  • react-redux 9.1.2

Here is the code in the shell app where I consume the dynamic remote

import React, { Suspense, useState, useEffect } from "react";
import { init, loadRemote } from "@module-federation/runtime";

init({
  name: "app1",
  remotes: [
    {
      name: "FITSample",
      entry: "http://localhost:3001/remoteEntry.js",
    },
  ],
});

function useDynamicImport({ module, scope }) {
  const [component, setComponent] = useState(null);

  useEffect(() => {
    if (!module || !scope) return;

    const loadComponent = async () => {
      try {
        const { default: Component } = await loadRemote(`${scope}/${module}`);
        setComponent(() => Component);
      } catch (error) {
        console.error(`Error loading remote module ${scope}/${module}:`, error);
      }
    };

    loadComponent();
  }, [module, scope]);

  return component;
}

function App() {
  const [{ module, scope }, setSystem] = useState({});

  const setsample = () => {
    setSystem({
      scope: "FITSample",
      module: "FITIntegrator",
    });
  };

  const Component = useDynamicImport({ module, scope });

  return (
    <div className="App">
      <h1>Bridge</h1>
      <button onClick={setsample}>Load Sample</button>
      <Suspense fallback="Loading integrator...">
        {Component ? <Component /> : null}
      </Suspense>
    </div>
  );
}

export default App;

Here is the remote app component that I exposed using module federation

import React, { Component } from "react";

import { Provider } from "react-redux";
import { CONSUMER_TYPES } from "./const/APP_DATA";
import storeConfigs from "./redux/store/Store";
import configAction from "./redux/action/configAction";

class FITIntegrator extends Component {
  constructor(props) {
    super(props);

    storeConfigs.store.dispatch(
      configAction.setConsumerConfig(CONSUMER_TYPES.XL)
    );
  }

  render() {
    return (
      <>
      <Provider store={storeConfigs.store}>
        <h1>Sample app</h1>
        </Provider>
      </>
    );
  }
}

export default FITIntegrator;

When shell app tries to render the remote it gives the following errors in the console

Warning: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:

  1. You might have mismatching versions of React and the renderer (such as React DOM)
  2. You might be breaking the Rules of Hooks
  3. You might have more than one copy of React in the same app

AND

react.development.js:1650 Uncaught TypeError: Cannot read properties of null (reading 'useMemo')

When I remove the Provider and the Store config from the constructor it works fine.

This is the module-federation sample that I followed
https://github.com/module-federation/module-federation-examples/blob/master/advanced-api/dynamic-remotes/app1/src/App.js

Have you shared React and Redux, etc. as a singleton?

Yes @ScriptedAlchemy both singleton: true and eager: true

do not eager share it

Send me your build configs too

also use registerRemote, not init since you are not passing shared modules in this init, just adding more remotes to the existing instance im guessing.

@ScriptedAlchemy here is a sample repo I created with the issue in the issue/dynamic-module branch
issue dynamic module with redux

Can you check this repo

Stop using eager: true for everything.
None of the other apps share Redux in the federation configuration either.

The host/shell does not pass any shares in its initialization because everything is eagerly shared and dynamically loaded. The system likely tears down the share scope because the host already uses eager React before the share initialization can complete. So multiple versions are loaded since the system later chose a different version.

Do not use eager: true.
You should also consider using rsbuild instead of CRA.

You also are not using v2. Should be using module-federation/enhanced/webpack

Not what's built into webpack. That's v1

@ScriptedAlchemy It worked with v2. Thanks for the support it means a lot.

Closing the issue because the solution was found.

Problem: The Redux provider cannot be exposed to the shared module when consuming as a dynamic remote module federation.
Solution: Upgrade module federation to v2 and remove eager consumptions from the config.

Below is the PR for the solution
Solution

Thank You!