Skip to content

Commit 86babb2

Browse files
authored
fix stale ensemble.user context in modal (#1098)
1 parent ca8322d commit 86babb2

File tree

8 files changed

+83
-20
lines changed

8 files changed

+83
-20
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@ensembleui/react-framework": patch
3+
---
4+
5+
fix stale ensemble.user in modal

packages/framework/src/evaluate/binding.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
createStorageApi,
99
screenStorageAtom,
1010
} from "../hooks/useEnsembleStorage";
11+
import { createUserApi } from "../hooks/useEnsembleUser";
1112
import { DateFormatter } from "../date/dateFormatter";
1213
import {
1314
themeAtom,
@@ -117,7 +118,7 @@ export const createBindingAtom = <T = unknown>(
117118
: undefined,
118119
),
119120
user: rawJsExpression.includes("ensemble.user")
120-
? get(userAtom)
121+
? createUserApi(() => get(userAtom))
121122
: undefined,
122123
env: rawJsExpression.includes("ensemble.env")
123124
? get(envAtom)

packages/framework/src/hooks/useCommandCallback.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import { useAtomCallback } from "jotai/utils";
22
import type { FC, ReactNode } from "react";
33
import { useCallback } from "react";
4-
import { mapKeys, assign } from "lodash-es";
4+
import { mapKeys } from "lodash-es";
55
import { createEvaluationContext } from "../evaluate";
6-
import type { EnsembleUser } from "../state";
76
import { appAtom, screenAtom, themeAtom, userAtom } from "../state";
87
import type { EnsembleContext, EnsembleLocation } from "../shared/ensemble";
98
import { DateFormatter } from "../date";
@@ -32,6 +31,7 @@ import type {
3231
} from "../shared";
3332
import { deviceAtom } from "./useDeviceObserver";
3433
import { createStorageApi, screenStorageAtom } from "./useEnsembleStorage";
34+
import { createUserApi } from "./useEnsembleUser";
3535
import { useCustomScope } from "./useCustomScope";
3636
import { useLanguageScope } from "./useLanguageScope";
3737

@@ -65,12 +65,16 @@ export const useCommandCallback = <
6565
const storage = get(screenStorageAtom);
6666
const device = get(deviceAtom);
6767
const theme = get(themeAtom);
68-
const user = get(userAtom);
6968

7069
const storageApi = createStorageApi(storage, (next) =>
7170
set(screenStorageAtom, next),
7271
);
7372

73+
const userApi = createUserApi(
74+
() => get(userAtom),
75+
(nextUser) => set(userAtom, nextUser),
76+
);
77+
7478
const customWidgets =
7579
applicationContext.application?.customWidgets.reduce(
7680
(acc, widget) => ({ ...acc, [widget.name]: widget }),
@@ -82,12 +86,7 @@ export const useCommandCallback = <
8286
screenContext,
8387
ensemble: {
8488
setTheme: (name: string) => set(themeAtom, name),
85-
user: {
86-
...user,
87-
set: (userUpdate: EnsembleUser) =>
88-
set(userAtom, assign({}, user, userUpdate)),
89-
setUser: (userUpdate: EnsembleUser) => set(userAtom, userUpdate),
90-
},
89+
user: userApi,
9190
storage: storageApi,
9291
formatter: DateFormatter(),
9392
env: applicationContext.env,

packages/framework/src/hooks/useEnsembleUser.ts

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { useAtom } from "jotai";
22
import { assign, isEmpty } from "lodash-es";
33
import { useMemo } from "react";
44
import { userAtom, type EnsembleUser } from "../state";
5+
import { getUserFromStorage } from "../utils/userStorage";
56

67
interface EnsembleUserBuffer {
78
set: (items: { [key: string]: unknown }) => void;
@@ -28,16 +29,31 @@ export const useEnsembleUser = (): EnsembleUser & EnsembleUserBuffer => {
2829

2930
// ensure first render on direct loads sees latest value from sessionStorage
3031
const sessionSnapshot = useMemo<EnsembleUser>(() => {
31-
try {
32-
const raw = sessionStorage.getItem("ensemble.user");
33-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
34-
return raw ? JSON.parse(raw) : {};
35-
} catch {
36-
return {};
37-
}
32+
return getUserFromStorage();
3833
}, []);
3934

4035
const effectiveUser = isEmpty(user) ? sessionSnapshot : user;
4136

4237
return { ...storageBuffer, ...effectiveUser };
4338
};
39+
40+
export const createUserApi = (
41+
getUser: () => EnsembleUser,
42+
setUser?: (user: EnsembleUser) => void,
43+
) => {
44+
const currentUser = getUser();
45+
return {
46+
...currentUser,
47+
set: (userUpdate: EnsembleUser): void => {
48+
const user = getUser();
49+
const updatedUser = assign({}, user, userUpdate);
50+
setUser?.(updatedUser);
51+
},
52+
setUser: (userUpdate: EnsembleUser): void => {
53+
setUser?.(userUpdate);
54+
},
55+
get: (key: string): unknown => {
56+
return getUser()[key];
57+
},
58+
};
59+
};

packages/framework/src/hooks/useScreenContext.tsx

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Provider, useAtom, useAtomValue, useSetAtom } from "jotai";
2-
import { useCallback, useContext } from "react";
2+
import { useCallback, useContext, useEffect } from "react";
33
import { clone, merge } from "lodash-es";
44
import { useHydrateAtoms } from "jotai/utils";
55
import {
@@ -10,7 +10,9 @@ import {
1010
screenDataAtom,
1111
screenModelAtom,
1212
themeModelAtom,
13+
userAtom,
1314
} from "../state";
15+
import { getUserFromStorage } from "../utils/userStorage";
1416
import type {
1517
ApplicationContextDefinition,
1618
ScreenContextActions,
@@ -74,17 +76,35 @@ const HydrateAtoms: React.FC<
7476
> = ({ appContext, screenContext, children }) => {
7577
const themeScope = useContext(CustomThemeContext);
7678

79+
const latestUserData = getUserFromStorage();
80+
7781
// initialising on state with prop on render here
7882
useHydrateAtoms([[screenAtom, screenContext]]);
7983
useHydrateAtoms([
8084
[appAtom, appContext],
8185
[themeModelAtom, themeScope.theme],
86+
[userAtom, latestUserData],
8287
]);
8388

8489
// initiate device resizer observer
8590
useDeviceObserver();
8691

87-
return <>{children}</>;
92+
return (
93+
<>
94+
<UserAtomMount />
95+
{children}
96+
</>
97+
);
98+
};
99+
100+
const UserAtomMount: React.FC = () => {
101+
const [user] = useAtom(userAtom);
102+
103+
useEffect(() => {
104+
// ensure userAtom is mounted in the modal's store
105+
}, [user]);
106+
107+
return null;
88108
};
89109

90110
/**

packages/framework/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ export * from "./date";
1010
export * from "./state";
1111
export * from "./evaluate";
1212
export * from "./appConfig";
13+
export * from "./utils/userStorage";

packages/framework/src/state/user.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { WritableAtom } from "jotai";
22
import { atom } from "jotai";
3+
import { setUserInStorage } from "../utils/userStorage";
34

45
export type EnsembleUser = { accessToken?: string } & {
56
[key: string]: unknown;
@@ -60,7 +61,11 @@ const atomWithSessionStorage = <T = unknown>(
6061
typeof update === "function" ? update(get(baseAtom)) : update
6162
) as T;
6263
set(baseAtom, nextValue);
63-
sessionStorage.setItem(key, JSON.stringify(nextValue));
64+
if (key === "ensemble.user") {
65+
setUserInStorage(nextValue);
66+
} else {
67+
sessionStorage.setItem(key, JSON.stringify(nextValue));
68+
}
6469
// notify other stores in this tab to update immediately
6570
if (typeof window !== "undefined") {
6671
window.dispatchEvent(new StorageEvent("storage", { key }));
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
export const getUserFromStorage = <T = unknown>(): T => {
2+
try {
3+
const raw = sessionStorage.getItem("ensemble.user");
4+
return (raw ? JSON.parse(raw) : {}) as T;
5+
} catch {
6+
return {} as T;
7+
}
8+
};
9+
10+
export const setUserInStorage = (user: unknown): void => {
11+
try {
12+
sessionStorage.setItem("ensemble.user", JSON.stringify(user));
13+
} catch (err) {
14+
// no-op
15+
}
16+
};

0 commit comments

Comments
 (0)