首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >状态被重新初始化为其原始值- useState或useReduce

状态被重新初始化为其原始值- useState或useReduce
EN

Stack Overflow用户
提问于 2020-12-08 10:24:48
回答 1查看 108关注 0票数 0

我创建了一个自定义钩子(useAuth)来扩展第三方身份验证服务Auth0的useAuth0钩子,并设置了一些保存基本用户信息的局部变量,如userId。

我有一个可以模拟其他帐户的主帐户。这意味着它从我的自定义钩子中重写了userId,并且它在整个系统中传播。

我面临的问题是,每当我调用改变这个钩子内部状态的impersonate函数时,它就会改变它,然后重新初始化它自己。我不知道是什么导致了重新初始化。代码在下面。

代码语言:javascript
复制
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).

  • I
  • 反复检查是否将正确的依赖项传递给了useEffect (这并不是导致
  • 在还原器之前使用useStae的原因,我是通过它的函数调用它而不是直接设置状态)。在整个周期中,我尝试了介入(调试),但没有发现任何东西。
  • ,我浏览了几个这样的帖子,并做出了反应,看看我是否能找到任何问题,但我失明的眼睛看不到它。

下面是我从哪里调用它的视图(请参阅const {impersonate} = useAuth()):

代码语言:javascript
复制
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;

下面是最初的提供者:

代码语言:javascript
复制
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);
EN

回答 1

Stack Overflow用户

发布于 2020-12-13 01:39:08

我觉得自己很迟钝。钩子正常工作。上面的方法没有什么问题(其他一些事情可以讨论)。问题是,我不是通过上下文传递钩子,而是调用每个组件上的钩子。这意味着钩子正在重新创建每个组件的状态(应该是这样),所以每当我更新状态时,它只会在一个组件中声明。

就是这样。我只需将钩子重写为组件并与上下文共享即可。

下面是最后的代码(由于简洁,省略了一些类型)

代码语言:javascript
复制
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/>组件放在我的应用程序的顶部。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/65197121

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档