Skip to content

Commit dfe19e3

Browse files
fix: stabilize env config fetching (#4364)
1 parent 2ad3429 commit dfe19e3

File tree

5 files changed

+39
-33
lines changed

5 files changed

+39
-33
lines changed

advanced-api/dynamic-remotes-runtime-environment-variables/e2e/checkDynamicRemotesRuntimesApps.spec.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@ async function openLocalhost(page: Page, port: number) {
1919
// Wait for the page to load but don't wait for networkidle since env loading might be polling
2020
await page.waitForLoadState('load');
2121

22-
// Wait for React to render - either the loading screen or the main content
23-
await page.waitForSelector('body > div', { timeout: 10000 });
22+
// Wait for the root element to be attached. It may not be visible immediately
23+
// (for example, the remote app shows an empty #root while it loads env config),
24+
// so we only wait for the element to exist in the DOM.
25+
await page.waitForSelector('#root', { state: 'attached', timeout: 10000 });
2426

2527
// Log any errors found
2628
if (pageErrors.length > 0) {

advanced-api/dynamic-remotes-runtime-environment-variables/host/src/components/App.js

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
1-
import React, { createContext } from 'react';
1+
import React, { createContext, useMemo } from 'react';
22
import Main from './Main';
33
import useFetchJson from '../hooks/useFetchJson';
44

55
export const EnvContext = createContext();
66

77
const App = () => {
8-
const { data, loading, error, retry } = useFetchJson(
9-
'/env-config.json',
10-
{
11-
maxRetries: 2,
12-
retryDelay: 500,
13-
timeout: 3000,
14-
validateData: (data) => data && typeof data === 'object',
15-
fallbackData: {
16-
API_URL: 'https://fallback.api.com',
17-
REMOTE_URL: 'http://localhost:3001/remoteEntry.js'
18-
}
8+
// Memoize options to avoid re-triggering fetch on each render in development
9+
const fetchOptions = useMemo(() => ({
10+
maxRetries: 2,
11+
retryDelay: 500,
12+
timeout: 3000,
13+
validateData: (data) => data && typeof data === 'object',
14+
fallbackData: {
15+
API_URL: 'https://fallback.api.com',
16+
REMOTE_URL: 'http://localhost:3001/remoteEntry.js'
1917
}
20-
);
18+
}), []);
19+
20+
const { data, loading, error, retry } = useFetchJson('/env-config.json', fetchOptions);
2121

2222
if (loading) {
2323
return (

advanced-api/dynamic-remotes-runtime-environment-variables/host/src/hooks/useFetchJson.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -124,18 +124,18 @@ const useFetchJson = (path, options = {}) => {
124124
}, [fetchData]);
125125

126126
useEffect(() => {
127+
// Component may mount twice in React 18 strict mode. Ensure the ref is
128+
// reset so subsequent fetches can update state correctly.
129+
isMountedRef.current = true;
127130
fetchData();
128-
}, [fetchData]);
129131

130-
// Cleanup on unmount
131-
useEffect(() => {
132132
return () => {
133133
isMountedRef.current = false;
134134
if (abortControllerRef.current) {
135135
abortControllerRef.current.abort();
136136
}
137137
};
138-
}, []);
138+
}, [fetchData]);
139139

140140
return {
141141
data,

advanced-api/dynamic-remotes-runtime-environment-variables/remote/src/components/WidgetWrapper.js

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,25 @@
1-
import React, { createContext } from 'react';
1+
import React, { createContext, useMemo } from 'react';
22
import Widget from './Widget';
33
import useFetchJson from '../hooks/useFetchJson';
44

55
export const EnvContext = createContext();
66

77
// Wraps the Widget component with the EnvContext
88
const WidgetWrapper = () => {
9+
// Memoize fetch options to prevent repeated fetching in React strict mode
10+
const fetchOptions = useMemo(() => ({
11+
maxRetries: 3,
12+
retryDelay: 1000,
13+
timeout: 5000,
14+
validateData: (data) => data && typeof data === 'object',
15+
fallbackData: {
16+
API_URL: 'https://remote.fallback.api.com'
17+
}
18+
}), []);
19+
920
const { data, loading, error, retry } = useFetchJson(
1021
`${__webpack_public_path__}env-config.json`,
11-
{
12-
maxRetries: 3,
13-
retryDelay: 1000,
14-
timeout: 5000,
15-
validateData: (data) => data && typeof data === 'object',
16-
fallbackData: {
17-
API_URL: 'https://remote.fallback.api.com'
18-
}
19-
}
22+
fetchOptions
2023
);
2124

2225
if (loading) {

advanced-api/dynamic-remotes-runtime-environment-variables/remote/src/hooks/useFetchJson.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -125,18 +125,19 @@ const useFetchJson = (path, options = {}) => {
125125
}, [fetchData]);
126126

127127
useEffect(() => {
128+
// Reset mount flag on each mount. React 18 strict mode mounts components
129+
// twice in development, so without resetting this flag the fetch results
130+
// would be ignored on the second mount.
131+
isMountedRef.current = true;
128132
fetchData();
129-
}, [fetchData]);
130133

131-
// Cleanup on unmount
132-
useEffect(() => {
133134
return () => {
134135
isMountedRef.current = false;
135136
if (abortControllerRef.current) {
136137
abortControllerRef.current.abort();
137138
}
138139
};
139-
}, []);
140+
}, [fetchData]);
140141

141142
return {
142143
data,

0 commit comments

Comments
 (0)