Skip to content

Conversation

@ScriptedAlchemy
Copy link
Member

Summary

This PR implements the rerender functionality requested in issue #4171 to prevent remote apps from being recreated when host components rerender.

Problem Solved

When a host component rerenders and passes new props to a remote app component created with createRemoteAppComponent, the entire remote app gets recreated instead of just updating with the new props. This causes:

  • Loss of internal component state
  • Performance issues due to unnecessary recreation
  • Poor user experience

Solution

Added a rerender option to createBridgeComponent that allows developers to control how their remote apps handle rerenders:

export default createBridgeComponent({ rootComponent: App, rerender: (props) => { // Custom rerender logic return { shouldRecreate: false }; } });

Changes Made

1. Type Definitions (types.ts)

  • Added rerender?: (props: RenderParams) => { shouldRecreate?: boolean } | void to ProviderFnParams<T>

2. Bridge Implementation (bridge-base.tsx)

  • Added component state tracking with componentStateMap and propsStateMap
  • Implemented custom rerender detection logic
  • Added support for shouldRecreate flag to control recreation behavior
  • Maintained backward compatibility for existing code

3. Test Suite (rerender-issue.spec.tsx)

  • Tests custom rerender callback functionality
  • Verifies backward compatibility without rerender option
  • Tests rerender functions returning void
  • Validates proper prop passing to custom rerender functions

Usage Examples

Prevent Recreation (Preserve State)

export default createBridgeComponent({ rootComponent: RemoteApp, rerender: (props) => { return { shouldRecreate: false }; // Preserve state } });

Conditional Recreation

export default createBridgeComponent({ rootComponent: RemoteApp, rerender: (props) => { const shouldRecreate = props.forceRecreate === true; return { shouldRecreate }; } });

Custom Logic with Default Behavior

export default createBridgeComponent({ rootComponent: RemoteApp, rerender: (props) => { // Custom logic here console.log('Custom rerender logic executed'); // Return void for default behavior } });

Benefits

  • State Preservation: Remote apps can maintain internal state during host rerenders
  • Performance: Avoid expensive component recreation when not needed
  • User Experience: No loss of user input or component state
  • Flexibility: Developers control when to recreate vs. preserve
  • Backward Compatibility: Existing code works unchanged

Testing

The implementation includes comprehensive tests that verify:

  • Custom rerender callback is properly invoked
  • Backward compatibility with existing code
  • Different return value scenarios (object vs. void)
  • Proper prop passing to rerender functions

Fixes #4171

philip-lempke and others added 4 commits October 27, 2025 12:49
path.join is not meant for URLs. Code does not work in Node > 22.11 and produces an Invalid URL. Fixing by setting the origin as the URL base.
Fixes CI format check failure by adding required trailing comma.
- Add rerender option to ProviderFnParams interface for custom rerender handling - Update bridge-base implementation to support custom rerender logic - Add component state tracking to detect rerenders vs initial renders - Preserve component state when shouldRecreate is false - Maintain backward compatibility for existing code - Add comprehensive test suite for rerender functionality Fixes #4171
@changeset-bot
Copy link

changeset-bot bot commented Oct 30, 2025

🦋 Changeset detected

Latest commit: e730e0d

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 37 packages
Name Type
@module-federation/bridge-react Patch
@module-federation/modern-js Patch
remote5 Patch
remote6 Patch
@module-federation/runtime Patch
@module-federation/enhanced Patch
@module-federation/rspack Patch
@module-federation/webpack-bundler-runtime Patch
@module-federation/sdk Patch
@module-federation/runtime-tools Patch
@module-federation/managers Patch
@module-federation/manifest Patch
@module-federation/dts-plugin Patch
@module-federation/third-party-dts-extractor Patch
@module-federation/devtools Patch
@module-federation/bridge-vue3 Patch
@module-federation/bridge-shared Patch
@module-federation/bridge-react-webpack-plugin Patch
@module-federation/retry-plugin Patch
@module-federation/data-prefetch Patch
@module-federation/rsbuild-plugin Patch
@module-federation/error-codes Patch
@module-federation/inject-external-runtime-core-plugin Patch
@module-federation/runtime-core Patch
create-module-federation Patch
@module-federation/cli Patch
@module-federation/rspress-plugin Patch
@module-federation/metro Patch
@module-federation/metro-plugin-rnef Patch
@module-federation/metro-plugin-rnc-cli Patch
@module-federation/nextjs-mf Patch
@module-federation/node Patch
@module-federation/storybook-addon Patch
@module-federation/modernjsapp Patch
@module-federation/esbuild Patch
@module-federation/utilities Patch
website-new Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@netlify
Copy link

netlify bot commented Oct 30, 2025

Deploy Preview for module-federation-docs ready!

Name Link
🔨 Latest commit e730e0d
🔍 Latest deploy log https://app.netlify.com/projects/module-federation-docs/deploys/6903e5295712fc0008be662b
😎 Deploy Preview https://deploy-preview-4172--module-federation-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 329 to 332
const url = new URL(
path.join(origin, typescriptFolderName, file),
path.join(typescriptFolderName, file),
origin,
).toString();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge URL builder loses remote pathname in type download

The URL construction now uses new URL(path.join(typescriptFolderName, file), origin) which resolves the relative path against the origin by replacing its pathname. If a remote serves types from a subpath (e.g. https://host/app-a), this produces https://host/federated-types/..., dropping the app-a segment and causing the subsequent download to look in the wrong location. The previous code preserved the full origin path. Include the origin pathname in the joined path so remotes hosted under subpaths still download correctly.

Useful? React with 👍 / 👎.

- Fix shouldRecreate: true to actually unmount and recreate the root - Implement proper root recreation with fresh React root instance - Add comprehensive test to verify recreation behavior - Ensure state is truly reset when shouldRecreate is true - Maintain proper cleanup of old roots before creating new ones - Add changeset for version bumping - Remove test-implementation.js file (tests are in proper package location) This addresses the issue where shouldRecreate: true was not actually recreating the component and resetting state as promised in the API.
- Fix shouldRecreate: true to actually unmount and recreate the root - Implement proper root recreation with fresh React root instance - Add comprehensive test to verify recreation behavior - Ensure state is truly reset when shouldRecreate is true - Maintain proper cleanup of old roots before creating new ones This addresses the issue where shouldRecreate: true was not actually recreating the component and resetting state as promised in the API.
@ScriptedAlchemy
Copy link
Member Author

@codex review pr

- Revert packages/typescript/src/plugins/FederatedTypesPlugin.ts to main branch version - This file was not intended to be part of the bridge-react rerender functionality PR - Keep PR focused only on bridge-react changes for issue #4171
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +170 to +185
} else {
// Custom rerender function requested recreation - unmount and recreate
LoggerInstance.debug(
`createBridgeComponent recreating component due to shouldRecreate: true >>>`,
info,
);

// Unmount the existing root to reset all state
if (root && 'unmount' in root) {
root.unmount();
LoggerInstance.debug(
`createBridgeComponent unmounted existing root >>>`,
info,
);
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Emit destroy hooks when recreating root

When rerender requests recreation, the code unmounts the existing root and immediately creates a fresh one but never emits beforeBridgeDestroy/afterBridgeDestroy. In the normal destroy path those hooks run and allow listeners to tear down resources. For components that subscribe to the lifecycle hooks (e.g. RemoteAppWrapper and plugin consumers), forcing recreation will leak listeners and leave caches stale because they never see the destroy event. Consider invoking the same lifecycle hooks around this manual unmount or delegating to the existing destroy() logic before creating the new root.

Useful? React with 👍 / 👎.

@ScriptedAlchemy
Copy link
Member Author

@codex address all pr comments.

@ScriptedAlchemy
Copy link
Member Author

@codex review pr

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +121 to +159
// Create a new BridgeWrapper with updated props for rerender
const {
moduleName: updatedModuleName,
basename: updatedBasename,
memoryRoute: updatedMemoryRoute,
fallback: updatedFallback,
...updatedPropsInfo
} = info;

const UpdatedBridgeWrapper = ({
basename,
}: {
basename?: string;
}) => (
<ErrorBoundary
FallbackComponent={
updatedFallback as React.ComponentType<FallbackProps>
}
>
<RawComponent
appInfo={{
moduleName: updatedModuleName,
basename,
memoryRoute: updatedMemoryRoute,
}}
propsInfo={
{
...updatedPropsInfo,
basename,
...(beforeBridgeRenderRes as any)?.extraProps,
} as T
}
/>
</ErrorBoundary>
);

const updatedRootComponentWithErrorBoundary = (
<UpdatedBridgeWrapper basename={updatedBasename} />
);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve component identity during rerender

When rerender returns { shouldRecreate: false }, the code still defines a new UpdatedBridgeWrapper function and element for every call before invoking root.render. Because a React component’s identity is the function reference, creating a new arrow function here causes React to unmount the previous tree and mount a new one on each host rerender, so internal state is still lost even though the option is meant to avoid recreation. The stored componentStateMap/propsStateMap are never reused to render the same element, so the new rerender API does not actually preserve state. A single stable wrapper component (or reusing the stored element) is needed to prevent unmounting.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector
Copy link

Summary

Testing

  • ⚠️ Tests not run (not requested)

View task →

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

3 participants