You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Expo(React Native)应用无法获取AuthProvider上下文的问题求助

Expo(React Native)应用无法获取AuthProvider上下文的问题求助

我是移动端开发的新手,最近在开发Expo + TypeScript的应用时,卡在了AuthContext的使用问题上,试了各种办法都没解决,来求助各位大佬!

先说明下技术栈:Expo + TypeScript,下面是我写的AuthContext.tsx文件,逻辑上我觉得是没问题的:

import React, {
  createContext,
  useContext,
  useEffect,
  useState,
  ReactNode,
} from "react";
import axios from "axios";
import * as SecureStore from "expo-secure-store";
import { ActivityIndicator } from "react-native-paper";
import { jwtDecode } from "jwt-decode";

type AuthContextType = {
  onRegister: (username: string, password: string) => Promise<any>;
  onLogin: (username: string, password: string) => Promise<any>;
  onLogout: () => Promise<void>;
  authState: AuthStateType;
  authData: AuthDataType;
};

type AuthStateType = {
  token: string | null;
  authenticated: boolean;
};

type AuthDataType = {
  id: number | null;
  username: string | null;
  iat: number | null;
  exp: number | null;
};

const AuthContext = createContext<AuthContextType | undefined>(undefined);

const TOKEN_KEY = "authToken";
const API_URL = "https://anonytext-backend.vercel.app/api";

const useAuth = (): AuthContextType => {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error("useAuth must be used within an AuthProvider");
  }
  return context;
};

type AuthProviderProps = {
  children: ReactNode;
};

const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
  const [loading, setLoading] = useState<boolean>(true);
  const [authState, setAuthState] = useState<AuthStateType>({
    token: null,
    authenticated: false,
  });
  const [authData, setAuthData] = useState<AuthDataType>({
    id: null,
    username: null,
    iat: null,
    exp: null,
  });

  const register = async (username: string, password: string): Promise<any> => {
    try {
      return await axios.post(`${API_URL}/auth/register`, {
        username,
        password,
      });
    } catch (e: any) {
      return {
        error: true,
        message: e.response?.data?.message || "An error occurred",
      };
    }
  };

  const login = async (username: string, password: string): Promise<any> => {
    try {
      const result = await axios.post(`${API_URL}/auth/login`, {
        username,
        password,
      });
      setAuthState({ token: result.data.access, authenticated: true });
      axios.defaults.headers.common[
        "Authorization"
      ] = `Bearer ${result.data.access}`;
      await SecureStore.setItemAsync(TOKEN_KEY, result.data.access);

      return result;
    } catch (e: any) {
      return {
        error: true,
        message: e.response?.data?.message || "An error occurred",
      };
    }
  };

  const logout = async (): Promise<void> => {
    await SecureStore.deleteItemAsync(TOKEN_KEY);
    axios.defaults.headers.common["Authorization"] = "";
    setAuthState({ token: null, authenticated: false });
  };

  useEffect(() => {
    const loadToken = async () => {
      const token = await SecureStore.getItemAsync(TOKEN_KEY);

      if (token) {
        axios.defaults.headers.common["Authorization"] = `Bearer ${token}`;
        setAuthState({ token, authenticated: true });
        setAuthData(jwtDecode(token));
      }
      setLoading(false);
    };

    loadToken();
  }, []);

  const contextData: AuthContextType = {
    onRegister: register,
    onLogin: login,
    onLogout: logout,
    authState,
    authData,
  };

  return (
    <AuthContext.Provider value={contextData}>
      {!loading ? children : <ActivityIndicator size={20} />}
    </AuthContext.Provider>
  );
};

export { AuthProvider, useAuth };

但我一直收到下面这个错误:

(NOBRIDGE) ERROR  Warning: Error: useAuth must be used within an AuthProvider

This error is located at:
    in RootLayout
    in Unknown (created by Route())
    in Suspense (created by Route())
    in Route (created by Route())
    in Route() (created by ContextNavigator)
    in RNCSafeAreaProvider (created by SafeAreaProvider)
    in SafeAreaProvider (created by wrapper)
    in wrapper (created by ContextNavigator)
    in ThemeProvider
    in EnsureSingleNavigator
    in BaseNavigationContainer
    in NavigationContainerInner (created by ContextNavigator)
    in ContextNavigator (created by ExpoRoot)
    in ExpoRoot (created by App)
    in App (created by ErrorOverlay)
    in ErrorToastContainer (created by ErrorOverlay)
    in ErrorOverlay (created by withDevTools(ErrorOverlay))
    in withDevTools(ErrorOverlay)
    in RCTView (created by View)
    in View (created by AppContainer)
    in RCTView (created by View)
    in View (created by AppContainer)
    in AppContainer
    in main(RootComponent)

错误提示说useAuth必须在AuthProvider内部使用,但我明明已经在App.tsx里把RootLayout用AuthProvider包裹起来了:

import React from "react";
import { AuthProvider } from "@/context/AuthContext";
import RootLayout from "./app/_layout";
import { NotifierWrapper } from "react-native-notifier";
import { GestureHandlerRootView } from "react-native-gesture-handler";

// Wrap the entire app with AuthProvider
export default function App() {
  return (
    <GestureHandlerRootView style={{ flex: 1 }}>
      <NotifierWrapper>
        <AuthProvider>
          <RootLayout />
        </AuthProvider>
      </NotifierWrapper>
    </GestureHandlerRootView>
  );
}

然后我的./app/_layout.tsx里是这样使用useAuth的:

import React, { useCallback } from "react";
import { Slot, Redirect } from "expo-router";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { useFonts } from "expo-font";
import * as SplashScreen from "expo-splash-screen";
import { useAuth } from "@/context/AuthContext";
import { ActivityIndicator } from "react-native-paper";

// Prevent the splash screen from hiding until the font is loaded
SplashScreen.preventAutoHideAsync();

export default function RootLayout() {
  const { authState } = useAuth(); // Directly access authState from context

  console.log("Auth Context State:", authState);

  const [fontsLoaded] = useFonts({
    FontAwesome6Pro: require("../assets/fonts/fa-regular-400.ttf"),
    FontAwesome6ProLight: require("../assets/fonts/fa-light-300.ttf"),
    FontAwesome6ProSolid: require("../assets/fonts/fa-solid-900.ttf"),
  });

  const onLayoutRootView = useCallback(async () => {
    if (fontsLoaded) {
      await SplashScreen.hideAsync();
    }
  }, [fontsLoaded]);

  if (!fontsLoaded) {
    return null; // Keep splash screen visible while loading fonts
  }

  // If authState is undefined, don't attempt to render protected routes yet
  if (authState === undefined) {
    return <ActivityIndicator size={20} />; // Show loading indicator if auth state is undefined
  }

  return (
    <GestureHandlerRootView style={{ flex: 1 }} onLayout={onLayoutRootView}>
      {authState.authenticated ? <Slot /> : <Redirect href="/login" />}
    </GestureHandlerRootView>
  );
}

我已经按照要求把需要使用useAuth的RootLayout放在了AuthProvider的子组件树里,但还是一直报这个错误,是不是我哪里漏了什么配置,或者代码里有什么细节没注意到?麻烦大家帮我看看问题出在哪了!

备注:内容来源于stack exchange,提问作者Chris

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.14 18:10:55