Skip to content

Commit 331df83

Browse files
authored
fix(react): Compatibility with React 18 useSyncExternalStore hook (#1798)
* Bump `@xstate/react` to `^3.0.0` * Add bunch of debug console logs * Solution first draft * Remove debugger * Remove one more debug statement * Remove _state from destructures * AuthMachineSend type fix * Pin @xstate/react * Revert spacing diffs * Update comments * More comment updates * Update AuthInterpreter type * Create green-cooks-clap.md * Add comment for anys * Remove getSnapshot parameter
1 parent 3729ac8 commit 331df83

File tree

5 files changed

+36
-46
lines changed

5 files changed

+36
-46
lines changed

.changeset/green-cooks-clap.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@aws-amplify/ui-react": patch
3+
"@aws-amplify/ui": patch
4+
---
5+
6+
fix(react): Add compatibility with React 18 `useSyncExternalStore` hook

packages/react/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@
6363
"@radix-ui/react-dropdown-menu": "0.1.6",
6464
"@radix-ui/react-slider": "0.1.4",
6565
"@radix-ui/react-tabs": "0.1.5",
66-
"@xstate/react": "1.6.3",
66+
"@xstate/react": "3.0.0",
6767
"classnames": "2.3.1",
6868
"deepmerge": "4.2.2",
6969
"lodash": "4.17.21",

packages/react/src/components/Authenticator/hooks/useAuthenticator/index.tsx

Lines changed: 9 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -121,52 +121,23 @@ export const useAuthenticator = (selector?: Selector) => {
121121
};
122122

123123
/**
124-
* For `useSelector`'s selector argument, we just return back the `state`.
125-
* The reason is that whenever you select a specific value of the state, the
126-
* hook will return *only* that selected value instead of the whole `state`.
124+
* For `useSelector`'s selector argument, we transform `state` into
125+
* public facade values using `getFacade`.
127126
*
128-
* To provide a consistent set of facade, we let the `selector` trivially return
129-
* itself and let comparator decide when to re-render.
127+
* This is to hide the internal xstate implementation details to customers.
130128
*/
131-
const xstateSelector = (state: AuthMachineState) => state;
132-
133-
/**
134-
* Holds a snapshot copy of last previous facade values. Will be used
135-
* on state changes to see if any of facade values have changed.
136-
*/
137-
const prevFacadeRef = React.useRef<ReturnType<typeof getFacade>>();
129+
const xstateSelector = (state: AuthMachineState) => getFacade(state);
138130

139131
/**
140132
* comparator decides whether or not the new authState should trigger a
141133
* re-render. Does a deep equality check.
142134
*/
143135
const comparator = (
144-
/**
145-
* We do not use `_prevState`, because it holds a *reference* to actor
146-
* object, of which value could easily mutate between compare calls.
147-
*
148-
* Instead, we'll use prevFacadeRef for comparison.
149-
*/
150-
_prevState: AuthMachineState,
151-
nextState: AuthMachineState
136+
prevFacade: ReturnType<typeof getFacade>,
137+
nextFacade: ReturnType<typeof getFacade>
152138
) => {
153139
if (!selector) return false;
154140

155-
/**
156-
* We only trigger re-render if any of values in specified selected
157-
* values change. First compute the facade for prev and next state.
158-
*/
159-
const prevFacade = prevFacadeRef.current;
160-
const nextFacade = getFacade(nextState);
161-
162-
/**
163-
* prevFacadeRef can now be updated with new facade values
164-
*/
165-
prevFacadeRef.current = nextFacade;
166-
167-
// If this is the first time comparator is called, return false
168-
if (!prevFacade) return false;
169-
170141
/**
171142
* Apply the passed in `selector` to get the value of their desired
172143
* dependency array.
@@ -179,12 +150,12 @@ export const useAuthenticator = (selector?: Selector) => {
179150
return areArrayValuesEqual(prevDepsArray, nextDepsArray);
180151
};
181152

182-
const state = useSelector(service, xstateSelector, comparator);
153+
const facade = useSelector(service, xstateSelector, comparator);
183154

184155
return {
185-
...getFacade(state),
156+
...facade,
186157
/** @deprecated For internal use only */
187-
_state: state,
158+
_state: service.getSnapshot(),
188159
/** @deprecated For internal use only */
189160
_send: send,
190161
};

packages/ui/src/types/authenticator/stateMachine/authMachine.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,16 @@ import { AuthEvent } from './event';
88

99
/**
1010
* Intefrace for `authMachine` machine interpreter
11+
*
12+
* TODO: tighten up anys here
1113
*/
12-
export type AuthInterpreter = Interpreter<AuthContext, any, AuthEvent>;
14+
export type AuthInterpreter = Interpreter<
15+
AuthContext,
16+
any,
17+
AuthEvent,
18+
any,
19+
any
20+
>;
1321

1422
/**
1523
* Function type for `send` in `authMachine`

yarn.lock

Lines changed: 11 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)