Skip to content

Commit 251f4ab

Browse files
committed
frontend: Initialize MSAL before use, use stable instance, and guard async init (fixes uninitialized_public_client_application); show small loading state
1 parent d49877d commit 251f4ab

File tree

1 file changed

+56
-26
lines changed

1 file changed

+56
-26
lines changed

app/frontend/src/layoutWrapper.tsx

Lines changed: 56 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,75 @@
1-
import { AccountInfo, EventType, PublicClientApplication } from "@azure/msal-browser";
1+
import { AuthenticationResult, EventType, PublicClientApplication } from "@azure/msal-browser";
22
import { checkLoggedIn, msalConfig, useLogin } from "./authConfig";
3-
import { useEffect, useState } from "react";
3+
import { useEffect, useMemo, useRef, useState } from "react";
44
import { MsalProvider } from "@azure/msal-react";
55
import { LoginContext } from "./loginContext";
66
import Layout from "./pages/layout/Layout";
77

88
const LayoutWrapper = () => {
99
const [loggedIn, setLoggedIn] = useState(false);
1010
if (useLogin) {
11-
var msalInstance = new PublicClientApplication(msalConfig);
11+
// Create a stable MSAL instance (avoid re-init/duplicate listeners; single shared client for MsalProvider).
12+
const msalInstance = useMemo(() => new PublicClientApplication(msalConfig), []);
13+
const [initialized, setInitialized] = useState(false);
14+
// Track mount state so we don't call setState after unmount if async init resolves late
15+
const mounted = useRef<boolean>(true);
1216

13-
// Default to using the first account if no account is active on page load
14-
if (!msalInstance.getActiveAccount() && msalInstance.getAllAccounts().length > 0) {
15-
// Account selection logic is app dependent. Adjust as needed for different use cases.
16-
msalInstance.setActiveAccount(msalInstance.getActiveAccount());
17-
}
17+
useEffect(() => {
18+
// React StrictMode in development invokes effects twice (mount -> cleanup -> mount).
19+
// Reset the flag here so this run is considered mounted.
20+
mounted.current = true;
21+
const init = async () => {
22+
try {
23+
await msalInstance.initialize();
1824

19-
// Listen for sign-in event and set active account
20-
msalInstance.addEventCallback(event => {
21-
if (event.eventType === EventType.LOGIN_SUCCESS && event.payload) {
22-
const account = event.payload as AccountInfo;
23-
msalInstance.setActiveAccount(account);
24-
}
25-
});
25+
// Default to using the first account if no account is active on page load
26+
if (!msalInstance.getActiveAccount() && msalInstance.getAllAccounts().length > 0) {
27+
msalInstance.setActiveAccount(msalInstance.getAllAccounts()[0]);
28+
}
2629

27-
useEffect(() => {
28-
const fetchLoggedIn = async () => {
29-
setLoggedIn(await checkLoggedIn(msalInstance));
30+
// Listen for sign-in event and set active account
31+
msalInstance.addEventCallback(event => {
32+
if (event.eventType === EventType.LOGIN_SUCCESS && event.payload) {
33+
const result = event.payload as AuthenticationResult;
34+
if (result.account) {
35+
msalInstance.setActiveAccount(result.account);
36+
}
37+
}
38+
});
39+
40+
if (mounted.current) {
41+
try {
42+
const isLoggedIn = await checkLoggedIn(msalInstance);
43+
setLoggedIn(isLoggedIn);
44+
} catch (e) {
45+
// Swallow check error but still allow app to render
46+
console.error("checkLoggedIn failed", e);
47+
}
48+
}
49+
} catch (e) {
50+
console.error("MSAL initialize failed", e);
51+
} finally {
52+
if (mounted.current) {
53+
setInitialized(true);
54+
}
55+
}
56+
};
57+
init();
58+
return () => {
59+
// On unmount: flag as unmounted so any pending async in init() doesn't call setState
60+
// This avoids React warnings about setting state on an unmounted component.
61+
mounted.current = false;
3062
};
63+
}, [msalInstance]);
3164

32-
fetchLoggedIn();
33-
}, []);
65+
if (!initialized) {
66+
// Lightweight placeholder while MSAL initializes
67+
return <p>Loading authentication…</p>;
68+
}
3469

3570
return (
3671
<MsalProvider instance={msalInstance}>
37-
<LoginContext.Provider
38-
value={{
39-
loggedIn,
40-
setLoggedIn
41-
}}
42-
>
72+
<LoginContext.Provider value={{ loggedIn, setLoggedIn }}>
4373
<Layout />
4474
</LoginContext.Provider>
4575
</MsalProvider>

0 commit comments

Comments
 (0)