我创建了一个自定义钩子(useAuth)来扩展第三方身份验证服务Auth0的useAuth0钩子,并设置了一些保存基本用户信息的局部变量,如userId。
我有一个可以模拟其他帐户的主帐户。这意味着它从我的自定义钩子中重写了userId,并且它在整个系统中传播。
我面临的问题是,每当我调用改变这个钩子内部状态的impersonate函数时,它就会改变它,然后重新初始化它自己。我不知道是什么导致了重新初始化。代码在下面。
import { useAuth0 } from '@auth0/auth0-react';
import produce from 'immer';
import { useState, useEffect, useCallback, useReducer, Reducer } from 'react';
import { AccountType, Auth0HookUser, TenantInfo, TenantType } from '../@dts';
type AuthVariants =
| 'INDIVIDUAL_TEACHER'
| 'INSTITUTION_TEACHER'
| 'STUDENT'
| 'SECRETARY'
| 'COORDINATOR'
| 'ADMINISTRATOR';
type AuthTenant = {
accountType: AccountType;
tenantType: TenantType;
employeeId: string;
tenantId: string;
selectedTenant: TenantInfo;
variant: AuthVariants;
mode: 'IMPERSONATION' | 'NORMAL';
user: Auth0HookUser;
};
const defaultAuthTenant: () => AuthTenant = () => ({
accountType: 'teacher',
employeeId: '',
mode: 'NORMAL',
selectedTenant: {
accountType: 'teacher',
tenantType: 'INSTITUTION',
tenantId: '',
},
tenantId: '',
tenantType: 'INSTITUTION',
variant: 'INDIVIDUAL_TEACHER',
user: {
name: '',
nickname: '',
} as any,
});
type Action =
| {
type: 'UPDATE_AUTH';
auth: AuthTenant;
}
| {
type: 'IMPERSONATE';
impersonatedEmployeeId: string;
impersonatedName: string;
accountType: AccountType;
}
| {
type: 'EXIT_IMPERSONATION';
};
type State = {
current: AuthTenant;
original: AuthTenant;
};
const reducer = produce((state: State, action: Action) => {
switch (action.type) {
case 'IMPERSONATE':
console.log('Impersonating');
const selectedTenant =
state.current.user['https://app.schon.io/user_data'].tenants[0];
state.current = {
...state.current,
user: {
...state.current.user,
name: action.impersonatedName,
nickname: action.impersonatedName,
'https://app.schon.io/user_data': {
...state.current.user['https://app.schon.io/user_data'],
userId: action.impersonatedEmployeeId,
},
},
mode: 'IMPERSONATION',
accountType: action.accountType,
employeeId: action.impersonatedEmployeeId,
variant: getVariant(action.accountType, selectedTenant.tenantType),
selectedTenant: {
...state.current.selectedTenant,
accountType: action.accountType,
},
};
return state;
case 'UPDATE_AUTH':
state.current = action.auth;
state.original = action.auth;
return state;
default:
return state;
}
});
export function useAuth() {
const { user: _user, isAuthenticated, isLoading, ...auth } = useAuth0();
const user = _user as Auth0HookUser;
const [selectedTenantIndex, setSelectedTenantIndex] = useState(0);
const [state, dispatch] = useReducer<Reducer<State, Action>>(reducer, {
current: defaultAuthTenant(),
original: defaultAuthTenant(),
});
const impersonate = (
impersonatedEmployeeId: string,
accountType: AccountType,
impersonatedName: string,
) => {
if (!user) {
return;
}
dispatch({
type: 'IMPERSONATE',
accountType,
impersonatedEmployeeId,
impersonatedName,
});
};
const exitImpersonation = useCallback(() => {
dispatch({ type: 'EXIT_IMPERSONATION' });
}, []);
useEffect(() => {
if (isLoading || (!isLoading && !isAuthenticated)) {
return;
}
if (!user || state.current.mode === 'IMPERSONATION') {
return;
}
console.log('Use Effect Running');
const { tenants, userId } = user['https://app.schon.io/user_data'];
const selectedTenant = tenants[selectedTenantIndex];
const { accountType, tenantType } = selectedTenant;
dispatch({
type: 'UPDATE_AUTH',
auth: {
tenantId: selectedTenant.tenantId,
employeeId: userId,
mode: 'NORMAL',
variant: getVariant(accountType, tenantType),
user,
selectedTenant,
accountType,
tenantType,
},
});
}, [
user,
isAuthenticated,
isLoading,
selectedTenantIndex,
state.current.mode,
]);
console.log('State Current', state.current);
return {
isAuthenticated,
isLoading,
impersonate,
exitImpersonation,
setSelectedTenantIndex,
...auth,
...state.current,
};
}
function getVariant(
accountType: AccountType,
tenantType: TenantType,
): AuthVariants {
if (accountType === 'teacher') {
return tenantType === 'INSTITUTION'
? 'INSTITUTION_TEACHER'
: 'INDIVIDUAL_TEACHER';
}
return accountType.toUpperCase() as AuthVariants;
}看这张照片。调用模拟函数后,它将其设置为模拟模式,但重新初始化自身并将其设置为默认模式。

这就是我尝试过的:
re-initialize).
下面是我从哪里调用它的视图(请参阅const {impersonate} = useAuth()):
import React, { memo, useCallback, useMemo, useState } from 'react';
import { RouteComponentProps } from '@reach/router';
import { Button, Typography } from 'components';
import Skeleton from 'react-loading-skeleton';
import { useAuth } from '../../../../../auth';
import { Tabs, Dialog } from '../../../../../components/';
import { useAllClassesAndTeacherForInstitution } from '../../../../../graphql';
import { useThemeSpacing } from '../../../../../shared-styles/material-ui';
import { AddClassTeacher, ListClassTeacher } from './components';
type TeacherViewRouteProps = {
teacherId: string;
};
export const TeacherView: React.FC<RouteComponentProps<
TeacherViewRouteProps
>> = memo((props) => {
const { impersonate } = useAuth();
const { teacherId } = props;
const { data, loading } = useAllClassesAndTeacherForInstitution(teacherId!);
const [open, setOpen] = useState(false);
const openDialog = useCallback(() => setOpen(true), []);
const closeDialog = useCallback(() => setOpen(false), []);
const spacing = useThemeSpacing(4)();
const teacherName = `${data?.teacher.name.fullName}`;
const impersonateTeacher = useCallback(() => {
if (!teacherName || !teacherId) {
return;
}
impersonate(teacherId!, 'teacher', teacherName);
closeDialog();
// props?.navigate?.('/');
}, [impersonate, closeDialog, teacherId, teacherName]);
const tabOptions = useMemo(
() => [
{
label: `Clases de ${teacherName}`,
},
{
label: 'Agregar Clases',
},
],
[teacherName],
);
return (
<>
<Typography variant="h1" className={spacing.marginTopBottom}>
{(loading && <Skeleton />) || teacherName}
</Typography>
<Dialog
title={`Entrar en la cuenta de ${teacherName}`}
open={open}
onAgree={impersonateTeacher}
onClose={closeDialog}
>
¿Desea visualizar la cuenta de {teacherName}?
<br />
Si desea salir de la misma por favor refresque la página.
</Dialog>
<Button className={spacing.marginTopBottom} onClick={openDialog}>
Entrar en cuenta de {teacherName || 'maestro'}
</Button>
{process.env.NODE_ENV === 'development' && (
<>
<Tabs options={tabOptions}>
<>
{data?.teacher.klasses && (
<ListClassTeacher
klasses={data.teacher.klasses}
teacherName={teacherName || 'maestro'}
/>
)}
</>
<>
{data?.grades && (
<AddClassTeacher
existingClasses={data?.teacher.klasses || []}
grades={data.grades}
teacherId={teacherId!}
/>
)}
</>
</Tabs>
</>
)}
</>
);
});
export default TeacherView;下面是最初的提供者:
import React, { Suspense, memo } from 'react';
import { Location } from '@reach/router';
import { ThemeProvider } from '@material-ui/core';
import { ApolloProvider } from '@apollo/client';
import { theme } from 'components';
import { Auth0Provider } from '@auth0/auth0-react';
import CircularLoader from './components/CircularProgress';
import { useGlobalClient } from './utilities/client';
import { Layout } from './views/Layout';
import { Root } from './views/Root';
import { enableIfNotPreRendering } from './utilities/isPrerendering';
import { AUTH_CONFIG } from './auth/auth0.variables';
console.log('AUTH CONFIG', AUTH_CONFIG);
function App() {
// This will be a method to enable faster loading times.
/**\
* Main AppMethod which hosts the site. To improve FCP it was split into
* 2 files: The main file which will load the <Home component without any
* dependencies (making it extremely fast to load at the beginning as it won't)
* download all the code on its entirety.
*
* All consumers that are descendants of a Provider will re-render whenever the Provider’s value prop changes.
* The propagation from Provider to its descendant consumers is not subject to the
* shouldComponentUpdate method, so the consumer is updated even when an ancestor component
* bails out of the update.
*
* Check this out whenever you're planning on implementing offline capabilities:
* https://dev.to/willsamu/how-to-get-aws-appsync-running-with-offline-support-and-react-hooks-678
*/
return (
<Suspense fallback={<CircularLoader scrollsToTop={true} />}>
<Location>
{({ location }) => (
<Auth0Provider
{...AUTH_CONFIG}
location={{ pathname: location.pathname, hash: location.hash }}
>
<ProviderForClient />
</Auth0Provider>
)}
</Location>
</Suspense>
);
}
/**
* This is done like this because we are using the useAuth0 Hook
* and we need it to be after the Auth0Provider!!
* @param props
*/
export const ProviderForClient: React.FC = (props) => {
const globalClient = useGlobalClient();
return (
<ThemeProvider theme={theme}>
<ApolloProvider client={globalClient.current as any}>
<Layout>
<>{enableIfNotPreRendering() && <Root />}</>
</Layout>
</ApolloProvider>
</ThemeProvider>
);
};
export default memo(App);发布于 2020-12-13 01:39:08
我觉得自己很迟钝。钩子正常工作。上面的方法没有什么问题(其他一些事情可以讨论)。问题是,我不是通过上下文传递钩子,而是调用每个组件上的钩子。这意味着钩子正在重新创建每个组件的状态(应该是这样),所以每当我更新状态时,它只会在一个组件中声明。
就是这样。我只需将钩子重写为组件并与上下文共享即可。
下面是最后的代码(由于简洁,省略了一些类型)
const AuthContext = createContext<Context>({
isAuthenticated: false,
isLoading: false,
getAccessTokenSilently() {
return '' as any;
},
getAccessTokenWithPopup() {
return '' as any;
},
getIdTokenClaims() {
return '' as any;
},
loginWithPopup() {
return '' as any;
},
loginWithRedirect() {
return '' as any;
},
logout() {
return '' as any;
},
impersonate(a: string, b: AccountType, c: string) {},
exitImpersonation() {},
setSelectedTenantIndex(i: number) {},
accountType: 'teacher',
employeeId: '',
mode: 'NORMAL',
selectedTenant: {
accountType: 'teacher',
tenantType: 'INSTITUTION',
tenantId: '',
},
tenantId: '',
tenantType: 'INSTITUTION',
user: {
name: '',
nickname: '',
} as any,
variant: 'INSTITUTION_TEACHER',
});
/**
* A custom wrapper for Auth. This allows us to set impersonation
*/
export const Auth: React.FC = memo((props) => {
const { user: _user, isAuthenticated, isLoading, ...auth } = useAuth0();
const user = _user as Auth0HookUser;
const defaultUser = useCallback(
(user?: Auth0HookUser) => getDefaultAuthTenant(user),
[],
);
const [selectedTenantIndex, setSelectedTenantIndex] = useState(0);
const [state, dispatch] = useReducer<Reducer<State, Action>>(reducer, {
current: defaultUser(_user),
original: defaultUser(_user),
});
const calledDispatch = useCallback(dispatch, [dispatch]);
const impersonate = useCallback(
(
impersonatedEmployeeId: string,
accountType: AccountType,
impersonatedName: string,
) => {
if (!user) {
return;
}
calledDispatch({
type: 'IMPERSONATE',
accountType,
impersonatedEmployeeId,
impersonatedName,
});
},
[calledDispatch, user],
);
const exitImpersonation = useCallback(() => {
dispatch({ type: 'EXIT_IMPERSONATION' });
}, []);
useEffect(() => {
if (isLoading || (!isLoading && !isAuthenticated)) {
return;
}
if (!user || state.current.mode === 'IMPERSONATION') {
return;
}
const { tenants, userId } = user['https://app.schon.io/user_data'];
const selectedTenant = tenants[selectedTenantIndex];
const { accountType, tenantType } = selectedTenant;
dispatch({
type: 'UPDATE_AUTH',
auth: {
tenantId: selectedTenant.tenantId,
employeeId: userId,
mode: 'NORMAL',
variant: getVariant(accountType, tenantType),
user,
selectedTenant,
accountType,
tenantType,
},
});
// eslint-disable-next-line
}, [
user,
isAuthenticated,
isLoading,
selectedTenantIndex,
state.current.mode,
]);
return (
<AuthContext.Provider
value={{
isAuthenticated,
isLoading,
impersonate,
exitImpersonation,
setSelectedTenantIndex,
...auth,
...state.current,
}}
>
{props.children}
</AuthContext.Provider>
);
});
export function useAuth() {
const context = useContext(AuthContext);
if (!context) {
throw new Error(
'You need to place the AuthContext below the Auth0Context and on top of the app',
);
}
return context;
}现在,我刚刚把<Auth/>组件放在我的应用程序的顶部。
https://stackoverflow.com/questions/65197121
复制相似问题