You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

React Native+Redux自动登录问题:useEffect重渲染致页面卡顿求助

Hey there! Let's break down why your auto-login flow is causing a freeze and fix it step by step.

First, let's identify the core issues in your code:

  1. Navigation + Redux state conflict: Your SplashScreen component wraps the entire navigation stack and depends on the auth?.login Redux state. When you dispatch the login action to update the auth state, it triggers a re-render of SplashScreen, which re-creates the navigation stack. This creates unnecessary re-renders and potential loop behavior.
  2. Async logic order issues: You're calling navigation.replace before await AsyncStorage.setItem('intro', 'true') in the intro flow—once you navigate away, the current component unmounts, so that setItem call will never execute.
  3. No race condition handling: If the component unmounts mid-asynchronous operation (e.g., user closes the app before auto-login finishes), leftover async calls can cause errors or unexpected behavior.
  4. Missing error handling: AsyncStorage operations can throw errors, and without catching them, they'll silently block your flow.

Here's the fixed implementation:

1. Refactor the navigation structure (split Splash from the main stack)

Move the splash logic to the root of your app, so it only renders once during startup, then switches to the proper navigation stack once loading is done.

import { useSelector, useDispatch } from 'react-redux';
import AsyncStorage from '@react-native-async-storage/async-storage';

const App = () => {
  const [isLoading, setIsLoading] = React.useState(true);
  const auth = useSelector(state => state.auth);
  const dispatch = useDispatch();

  React.useEffect(() => {
    let isMounted = true; // Track if component is still mounted

    const tryAutoLogin = async () => {
      try {
        // Fetch stored data
        const authToken = await AsyncStorage.getItem('authToken');
        const user = await AsyncStorage.getItem('user');
        const introComplete = await AsyncStorage.getItem('intro');

        // Simulate loading (you can remove this in production)
        await new Promise(res => setTimeout(res, 2000));

        if (!isMounted) return; // Exit if component unmounted mid-flow

        // Handle intro flow first
        if (!introComplete) {
          await AsyncStorage.setItem('intro', 'true');
          setIsLoading(false);
          return;
        }

        // Handle auto-login if token exists
        if (authToken) {
          await dispatch(login({
            login: true,
            authToken,
            user: JSON.parse(user || '{}')
          }));
        }
      } catch (err) {
        console.error('Auto-login failed:', err);
        // Fallback to login screen on error
        if (isMounted) {
          // Handle error navigation here if needed
        }
      } finally {
        if (isMounted) setIsLoading(false);
      }
    };

    tryAutoLogin();

    // Cleanup: Mark component as unmounted
    return () => {
      isMounted = false;
    };
  }, [dispatch]);

  // Show splash while loading
  if (isLoading) {
    return <YourSplashLayoutComponent />; // Pure UI, no navigation logic
  }

  // Render proper navigation stack once loading is done
  return (
    <NavigationContainer>
      <Stack.Navigator>
        {/* Intro screen only if not completed */}
        {!await AsyncStorage.getItem('intro') ? (
          <Stack.Screen 
            name={RoutesName.Intro} 
            component={Intro} 
            options={noHeaderOption} 
          />
        ) : auth?.login ? (
          {/* Home screen if authenticated */}
          <Stack.Screen 
            name={RoutesName.Home} 
            component={Home} 
            options={noHeaderOption} 
          />
        ) : (
          {/* Auth screen if not authenticated */}
          <Stack.Screen 
            name={RoutesName.Auth} 
            component={Auth} 
            options={noHeaderOption} 
          />
        )}
      </Stack.Navigator>
    </NavigationContainer>
  );
};

2. Fix the login action (if using Redux Thunk)

Ensure your login action returns a promise so you can await it properly:

export const login = (credentials) => async (dispatch) => {
  try {
    dispatch({
      type: 'LOGIN_SUCCESS',
      payload: credentials
    });
  } catch (error) {
    dispatch({
      type: 'LOGIN_FAILURE',
      payload: error.message
    });
    throw error; // Let the caller handle the error
  }
};

Key fixes explained:

  • Separate splash from navigation: The splash screen only renders during initial load, so Redux state changes won't trigger re-renders of the entire navigation stack.
  • Race condition protection: The isMounted flag stops async operations from running after the component unmounts.
  • Proper async order: We execute AsyncStorage.setItem before finishing the intro flow, ensuring it runs before navigating away.
  • Error handling: We catch AsyncStorage and login errors to prevent silent failures.

内容的提问来源于stack exchange,提问作者Tarun Bhardwaj

火山引擎 最新活动