Javascript Firebase auth.currentUser在加载页面时为null,在页面加载数毫秒后调用authstatechange时加载用户

Javascript Firebase auth.currentUser在加载页面时为null,在页面加载数毫秒后调用authstatechange时加载用户,javascript,reactjs,firebase,firebase-authentication,Javascript,Reactjs,Firebase,Firebase Authentication,我正在使用React和Firebase开发一个小型web应用程序。 为了进行身份验证,我使用上下文API,并在上下文中添加loggedin用户详细信息 AuthProvider.tsx const AuthProvider:React.FC=props=>{ const[state,setState]=useState(authInitialState); log(“内部AuthProvider”); useffect(()=>{ auth.onAuthStateChanged(用户=>{ lo

我正在使用React和Firebase开发一个小型web应用程序。 为了进行身份验证,我使用上下文API,并在上下文中添加loggedin用户详细信息

AuthProvider.tsx

const AuthProvider:React.FC=props=>{
const[state,setState]=useState(authInitialState);
log(“内部AuthProvider”);
useffect(()=>{
auth.onAuthStateChanged(用户=>{
log(“身份验证状态已更改,用户为------>”,用户);
如果(用户){
log(“更新到上下文的用户值”)
设定状态({
…国家,
已验证:!!用户,
权限:[],
用户:用户
});
}
常量状态更改={
…国家,
已验证:!!用户,
用户
};
//如果(!用户){
返回设置状态(stateChange);
// }
});
}, []);
log(“呈现AuthProvider”,状态);
返回(
{props.children}
);
};
导出默认AuthProvider;
AuthConsumer.tsx

const with authContext=(
组件:React.ComponentClass | React.FunctionComponent
) => {
console.log(“呈现AuthConsumer for”);
返回(道具:任意)=>(
{context=>}
);
};
使用AuthContext导出默认值;
PrivateRoute.tsx

接口PrivateRouteProps扩展了RouteProps{
//tslint:禁用下一行:无任何
组成部分:任何;
语境:IAuthContext;
}
const PrivateRoute=(props:PrivateRouteProps)=>{
const{component:component,context:IAuthContext,…rest}=props;
控制台日志(“专用路径”,道具);
返回(
props.context.isAuthenticated(
) : (
)
}
/>
);
};
使用AuthContext导出默认值(PrivateRoute);
App.tsx

返回(
);
用户已登录,会话持久性设置为本地。 问题是,当我尝试localhost/subscription时,这是一个私有路由,context.isAuthenticated为false,因为“onAuthStateChanged”观察者尚未触发,因此它将进入登录页面,但几毫秒后,authStateChange被触发并设置了上下文,但它并没有用,因为应用程序已经导航到登录,因为privateroute认为用户并没有登录。
我想了解如何克服这个问题。

当页面加载Firebase时,从本地存储恢复用户的凭据,并与服务器检查它们是否仍然有效。由于这是对服务器的调用,因此可能需要一些时间并以异步方式进行。这就是为什么
firebase.auth().currentUser
在激发
onAuthStateChanged
之前为
null
是正常的

您遇到的问题是有多个原因
firebase.auth()。currentUser
可以是
null

  • firebase.auth().currentUser
    null
    ,因为客户端刚刚加载,并且仍在与服务器检查凭据
  • firebase.auth().currentUser
    null
    ,因为客户端根据服务器检查了凭据,但客户端未登录
  • firebase.auth().currentUser
    null
    ,因为用户从未登录,因此没有要检查的凭据
  • 您希望在案例2和案例3中导航,但不希望在案例1中导航

    典型的解决方案是在第一次触发onAuthStateChanged之前不处理导航。此时,您可以确定已针对服务器检查了凭据,或者没有要检查的凭据,在这两种情况下,您都希望导航到登录页面


    另外一个加速的方法是,当用户第一次登录时,自己在本地存储中存储一个小令牌,然后在应用加载时读取该令牌。如果存在令牌,您可以区分案例1和案例3,并使用它们更快地导航到正确的页面

    有关此示例,请参阅此I/O讨论

    const AuthProvider: React.FC = props => {
      const [state, setState] = useState<IAuthContext>(authInitialState);
      console.log("Inside AuthProvider");
      useEffect(() => {
        auth.onAuthStateChanged( user => {
          console.log("Auth State changed and user is ----->", user);
          if (user) {
            console.log("User value updated to the context")
            setState({
                ...authInitialState,
              isAuthenticated:!!user,
              permissions:[],
              user:user
            });
          }
          const stateChange = {
            ...authInitialState,
            isAuthenticated: !!user,
            user
          };
          // if (!user) {
            return setState(stateChange);
          // }
        });
      }, []);
      console.log("Rendering AuthProvider", state);
      return (
        <AuthContext.Provider value={state}>{props.children}</AuthContext.Provider>
      );
    };
    export default AuthProvider;
    
    const withAuthContext = (
      Component: React.ComponentClass<any> | React.FunctionComponent<any>
    ) => {
      console.log("Rendering AuthConsumer for ");
      return (props: any) => (
        <AuthContext.Consumer>
          {context => <Component {...props} context={context} />}
        </AuthContext.Consumer>
      );
    };
    
    export default withAuthContext;
    
    interface PrivateRouteProps extends RouteProps {
        // tslint:disable-next-line:no-any
        component: any;
        context: IAuthContext;
    }
    
    const PrivateRoute = (props: PrivateRouteProps) => {
        const { component: Component, context: IAuthContext, ...rest } = props;
        console.log("Private route for ", props);
        return (
            <Route
                {...rest}
                render={(routeProps) =>
                    props.context.isAuthenticated ? (
                        <Component {...routeProps} />
                    ) : (
                        <Redirect
                            to={{
                                pathname: '/login',
                                state: { from: routeProps.location }
                            }}
                        />
                    )
                }
            />
        );
    };
    
    export default withAuthContext(PrivateRoute);
    
    return (
            <BrowserRouter>
                <div>
                    <Switch>
                        <PublicRoute path="/frame" component={Frame} exact isAuthorized={true}/>
                        <Route path="/login" component={NewLogin} exact isAuthorized={true}/>
                        <PrivateRoute path="/nav" component={NavigationBar} exact/>
                        <PrivateRoute path="/dashboard" component={AnalyticsDashBoard} exact/>
                        <PrivateRoute path="/subscription" component={OrderSuccess} exact/>
                        <PrivateRoute path="/onboarding" component={OnBoarding} exact/>
                    </Switch>
                </div>
            </BrowserRouter>
        );