Skip to content

Commit e73daa7

Browse files
christopherthielenmergify[bot]
authored andcommitted
fix(uiCanExit): fix uiCanExit logic and use refs so hook de/registration happens outside any render
1 parent 55d4c98 commit e73daa7

File tree

1 file changed

+34
-17
lines changed

1 file changed

+34
-17
lines changed

src/components/UIView.tsx

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ function useRoutedComponentProps(
151151
return useMemo(() => ({ ...baseChildProps, ...maybeRefProp }), [baseChildProps, maybeRefProp]);
152152
}
153153

154+
/** @hidden */
154155
function useViewConfig() {
155156
const [viewConfig, setViewConfig] = useState<ReactViewConfig>();
156157
const viewConfigRef = useRef(viewConfig);
@@ -163,33 +164,49 @@ function useViewConfig() {
163164
return { viewConfig, configUpdated };
164165
}
165166

167+
/** @hidden */
166168
function useReactHybridApi(ref: React.Ref<unknown>, uiViewData: ActiveUIView, uiViewAddress: UIViewAddress) {
167169
const reactHybridApi = useRef({ uiViewData, uiViewAddress });
168170
reactHybridApi.current.uiViewData = uiViewData;
169171
reactHybridApi.current.uiViewAddress = uiViewAddress;
170172
useImperativeHandle(ref, () => reactHybridApi.current);
171173
}
172174

173-
// If a class component is being rendered, wire up its uiCanExit method
174-
// Return a { ref: Ref<ClassComponentInstance> } if passed a component class
175-
// Return an empty object {} if passed anything else
176-
// The returned object should be spread as props onto the child component
175+
/**
176+
* If a class component is being rendered, wire up its uiCanExit method
177+
* Return a { ref: Ref<ClassComponentInstance> } if passed a component class
178+
* Return an empty object {} if passed anything else
179+
* The returned object should be spread as props onto the child component
180+
* @hidden
181+
*/
177182
function useUiCanExitClassComponentHook(router: UIRouter, stateName: string, maybeComponentClass: any) {
178-
const ref = useRef<any>();
179-
const isComponentClass = maybeComponentClass?.prototype?.render || maybeComponentClass?.render;
180-
const componentInstance = isComponentClass && ref.current;
181-
const uiCanExit = componentInstance?.uiCanExit;
182-
183-
useEffect(() => {
184-
if (uiCanExit) {
185-
const deregister = router.transitionService.onBefore({ exiting: stateName }, uiCanExit.bind(ref.current));
186-
return () => deregister();
187-
} else {
188-
return () => undefined;
183+
// Use refs and run the callback outside of any render pass
184+
const componentInstanceRef = useRef<any>();
185+
const deregisterRef = useRef<Function>(() => undefined);
186+
187+
function callbackRef(componentInstance) {
188+
// Use refs
189+
const previous = componentInstanceRef.current;
190+
const deregisterPreviousTransitionHook = deregisterRef.current;
191+
192+
if (previous !== componentInstance) {
193+
componentInstanceRef.current = componentInstance;
194+
deregisterPreviousTransitionHook();
195+
196+
const uiCanExit = componentInstance?.uiCanExit;
197+
if (uiCanExit) {
198+
const boundCallback = uiCanExit.bind(componentInstance);
199+
deregisterRef.current = router.transitionService.onBefore({ exiting: stateName }, boundCallback);
200+
} else {
201+
deregisterRef.current = () => undefined;
202+
}
189203
}
190-
}, [uiCanExit]);
204+
}
191205

192-
return useMemo(() => (isComponentClass ? { ref } : undefined), [isComponentClass, ref]);
206+
return useMemo(() => {
207+
const isComponentClass = maybeComponentClass?.prototype?.render || maybeComponentClass?.render;
208+
return isComponentClass ? { ref: callbackRef } : undefined;
209+
}, [maybeComponentClass]);
193210
}
194211

195212
const View = forwardRef(function View(props: UIViewProps, forwardedRef) {

0 commit comments

Comments
 (0)