【问题标题】:How to manage UX auth error with react-native?如何使用 react-native 管理 UX 身份验证错误?
【发布时间】:2020-04-17 15:20:14
【问题描述】:

我在react-navigation auth flow guideFeathersJS react-native client 之后设置了一个基本的身份验证系统。

这是我的主要index.tsx 文件:

import 'react-native-gesture-handler';
import React, {
  useReducer, useEffect, useMemo, ReactElement,
} from 'react';
import { Platform, StatusBar } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { AppLoading } from 'expo';
import { useFonts } from '@use-expo/font';
import SplashScreen from './screens/SplashScreen';
import { AuthContext } from './contexts';
import { client } from './utils';
import DrawerNavigator from './navigation/DrawerNavigator';
import LogonStackNavigator from './navigation/LogonStackNavigator';

interface State {
  isLoading: boolean;
  isSignOut: boolean;
  userToken: string|null;
}

const App = (): ReactElement => {
  /* eslint-disable global-require */
  const [fontsLoaded] = useFonts({
    'Lato-Regular': require('../assets/fonts/Lato-Regular.ttf'),
    'Lato-Bold': require('../assets/fonts/Lato-Bold.ttf'),
    'Poppins-Light': require('../assets/fonts/Poppins-Light.ttf'),
    'Poppins-Bold': require('../assets/fonts/Poppins-Bold.ttf'),
  });
  /* eslint-enable global-require */

  const [state, dispatch] = useReducer(
    (prevState: State, action): State => {
      switch (action.type) {
        case 'RESTORE_TOKEN':
          return {
            ...prevState,
            userToken: action.token,
            isLoading: false,
          };
        case 'SIGN_IN':
          return {
            ...prevState,
            isSignOut: false,
            userToken: action.token,
          };
        case 'SIGN_OUT':
          return {
            ...prevState,
            isSignOut: true,
            userToken: null,
          };
        default:
          return prevState;
      }
    },
    {
      isLoading: true,
      isSignOut: false,
      userToken: null,
    },
  );

  useEffect(() => {
    const bootstrapAsync = async (): Promise<void> => {
      let userToken;

      try {
        const auth = await client.reAuthenticate();
        console.log('reAuthenticate:', auth);
        userToken = auth.accessToken;
      } catch (e) {
        // eslint-disable-next-line no-console
        console.log('reAuthenticate failure:', e);
      }

      dispatch({
        type: 'RESTORE_TOKEN',
        token: userToken,
      });
    };

    bootstrapAsync();
  }, []);

  const authContext = useMemo(
    () => ({
      signIn: async (data) => {
        // In a production app, we need to send some data (usually username, password) to server and get a token
        // We will also need to handle errors if sign in failed
        // After getting token, we need to persist the token using `AsyncStorage`
        // In the example, we'll use a dummy token
        // eslint-disable-next-line no-console
        console.log('signIn', data);

        try {
          const auth = await client.authenticate({
            strategy: 'local',
            ...data,
          });
          console.log(auth);
          dispatch({
            type: 'SIGN_IN',
            token: 'dummy-auth-token',
          });
        } catch (e) {
          console.log('signIn failure:', e);
        }
      },
      signOut: async () => {
        try {
          await client.logout();
          dispatch({ type: 'SIGN_OUT' });
        } catch (e) {
          console.log('signOut failure:', e);
        }
      },
      signUp: async (data) => {
        // In a production app, we need to send user data to server and get a token
        // We will also need to handle errors if sign up failed
        // After getting token, we need to persist the token using `AsyncStorage`
        // In the example, we'll use a dummy token
        // eslint-disable-next-line no-console
        console.log('signUp', data);

        dispatch({
          type: 'SIGN_IN',
          token: 'dummy-auth-token',
        });
      },
    }),
    [],
  );

  if (!fontsLoaded) {
    return <AppLoading />;
  }

  if (state.isLoading) {
    return <SplashScreen />;
  }

  return (
    <AuthContext.Provider value={authContext}>
      {Platform.OS === 'ios' && (
        <StatusBar barStyle="dark-content" />
      )}
      {state.userToken == null ? (
        <NavigationContainer>
          <LogonStackNavigator />
        </NavigationContainer>
      ) : (
        <NavigationContainer>
          <DrawerNavigator />
        </NavigationContainer>
      )}
    </AuthContext.Provider>
  );
};

export default App;

还有我处理登录表单的SiginScreen.tsx 文件:

import React, { ReactElement } from 'react';
import { StyleSheet, KeyboardAvoidingView } from 'react-native';
import { StackNavigationProp } from '@react-navigation/stack';
import { RouteProp } from '@react-navigation/core';
import { AuthContext } from '../contexts';
import {
  LogonHeader, Button, Input, Text, TextLink,
} from '../components';
import { LogonStackParamList } from '../navigation/LogonStackNavigator';

interface Props {
  navigation: StackNavigationProp<LogonStackParamList, 'SignIn'>;
  route: RouteProp<LogonStackParamList, 'SignIn'>;
}

const SignInScreen = ({ navigation }: Props): ReactElement => {
  const [email, setEmail] = React.useState('');
  const [password, setPassword] = React.useState('');

  const { signIn } = React.useContext(AuthContext);

  return (
    <KeyboardAvoidingView
      style={styles.container}
      behavior="padding"
    >
      <LogonHeader title="Se connecter" />
      <Input
        placeholder="E-mail"
        value={email}
        onChangeText={setEmail}
        keyboardType="email-address"
      />
      <Input
        placeholder="Mot de passe"
        value={password}
        onChangeText={setPassword}
        secureTextEntry
      />
      <Button
        title="Connexion"
        onPress={() => signIn({
          email,
          password,
        })}
      />
    </KeyboardAvoidingView>
  );
};

export default SignInScreen;

它按预期工作,但我不知道如何处理错误情况。

目前,它只是 index.tsx 文件上的 console.log 声明。

如何正确通知SignInScreen 组件登录失败在用户端显示消息?我应该使用 redux 还是什么?

更确切地说:我想直接在SignInScreen 上放一条错误短信,以防万一失败。

【问题讨论】:

    标签: reactjs typescript react-native authentication


    【解决方案1】:

    正如我所见,您可以向您的global state 添加一个新属性,它可能是null,指示给定的身份验证错误,然后您可以在每次服务返回错误时进行调度。如果您愿意改变您的方法,我建议您仅将用户令牌存储在全局状态中,您可以在每个屏幕中单独管理isLoading,并且可以从令牌中派生isSignOut。您将减少重新渲染的次数并简化其背后的逻辑。

    编辑: 这是您可以执行的代码示例:

    // main index.tsx
    import 'react-native-gesture-handler';
    import React, {
      useReducer, useEffect, useMemo, ReactElement,
    } from 'react';
    import { Platform, StatusBar } from 'react-native';
    import { NavigationContainer } from '@react-navigation/native';
    import { AppLoading } from 'expo';
    import { useFonts } from '@use-expo/font';
    import SplashScreen from './screens/SplashScreen';
    import { AuthContext } from './contexts';
    import { client } from './utils';
    import DrawerNavigator from './navigation/DrawerNavigator';
    import LogonStackNavigator from './navigation/LogonStackNavigator';
    
    interface State {
      isLoading: boolean;
      isSignOut: boolean;
      userToken: string|null;
      //we add a new property to your state object to make visible the errors
      authError: string|null;
    }
    
    const App = (): ReactElement => {
      // keep the same code, deleted for simplicity
    
      const [state, dispatch] = useReducer(
        (prevState: State, action): State => {
          switch (action.type) {
            case 'RESTORE_TOKEN':
              return {
                ...prevState,
                authError: null,
                userToken: action.token,
                isLoading: false,
              };
            case 'SIGN_IN':
              return {
                ...prevState,
                isSignOut: false,
                authError: null,
                userToken: action.token,
              };
            case 'SIGN_OUT':
              return {
                ...prevState,
                isSignOut: true,
                authError: null,
                userToken: null,
              };
            case: 'AUTH_ERROR':
              return {
                ...prevState,
                authError: action.error
              };
            default:
              return prevState;
          }
        },
        {
          isLoading: true,
          isSignOut: false,
          userToken: null,
          authError: null,
        },
      );
    
      useEffect(() => {
        const bootstrapAsync = async (): Promise<void> => {
    
          try {
            const auth = await client.reAuthenticate();
            console.log('reAuthenticate:', auth);
            dispatch({
              type: 'RESTORE_TOKEN',
              token: auth.accessToken,
            });
          } catch (e) {
            // eslint-disable-next-line no-console
            console.log('reAuthenticate failure:', e);
            dispatch({
              type: 'AUTH_ERROR',
              token: e, //or what ever your want to show
            });
          }
        };
    
        bootstrapAsync();
      }, []);
    
      const authContext = useMemo(
        () => ({
        //we add state here in order to access it from 
        //React.useContext(AuthContext);
           state,
        //put your code here
        }),
        [],
      );
    
    /*
    ...YOUR CODE...
    */ 
    
    

    基本上,在上面的代码中,我们为您的全局状态添加了一个新属性 authError。然后,您需要一个新操作来更新该属性。然后,在您的 authContext 中,我们添加要从任何地方检索的全局状态。 为了简单起见,我只是在您的代码中添加了一个带有AUTH_ERROR 操作的调度,但您可以在signInsignUpsignOut 中执行相同的操作。

    在您的SiginScreen.tsx 中,您可以执行以下操作:

    const SignInScreen = ({ navigation }: Props): ReactElement => {
      const [email, setEmail] = React.useState('');
      const [password, setPassword] = React.useState('');
    
      //we get the state we added in our AuthContext
      const { signIn, state } = React.useContext(AuthContext);
    
      return (
        <KeyboardAvoidingView
          style={styles.container}
          behavior="padding"
        >
          <LogonHeader title="Se connecter" />
          <Input
            placeholder="E-mail"
            value={email}
            onChangeText={setEmail}
            keyboardType="email-address"
          />
          <Input
            placeholder="Mot de passe"
            value={password}
            onChangeText={setPassword}
            secureTextEntry
          />
          <Button
            title="Connexion"
            onPress={() => signIn({
              email,
              password,
            })}
          />
        {/* shows the error if any, otherwise it won't be shown */}
        {state.authError && <Text>{state.authError}</Text>}
        </KeyboardAvoidingView>
      );
    };
    
    

    【讨论】:

    • 我不确定是否会关注你。另外,isLoading 仅用于 SplashScreen。在这里,我想在 SigninScreen 上添加一些文本以防失败。您可以添加一些代码示例吗?
    • 非常感谢!与此同时,我找到了另一种方法:我在我的 signInScreen 上捕获了来自 signIn 方法的异常抛出。但我不知道这是否比您的解决方案更好......
    • @Soullivaneuh 我会说是的。我个人在屏幕级别管理此身份验证流程。这样做可以避免一些可能影响应用性能的重新渲染,除非您需要从另一个屏幕了解错误。这就是我在我的建议中的意思,我认为你无法通过你构建项目的方式来做到这一点
    • 不确定是否理解您的最后评论。所以根据最佳实践,你给我的状态方法比我的解决方案更好?
    • @Soullivaneuh 不,先生,看看你的问题。你的做法更好:)
    猜你喜欢
    • 2016-06-12
    • 2018-06-19
    • 1970-01-01
    • 1970-01-01
    • 2019-12-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-30
    相关资源
    最近更新 更多