React Router Dom & Firebase Auth: history.push 更改 url,但在 Firebase auth 创建帐户后调用时不会呈现新组件



我要做的
我使用Firebase v9和React路由器dom v5.3.0制作一个注册表单,该表单创建一个新帐户,并在创建帐户时将用户重定向到主屏幕。主屏幕的路线是"/&";。

问题
我的计划是在调用注册功能后调用history.push("/"),这应该会将我带到主屏幕。然而,在运行代码时,history.push("/")只更新了URL,没有将我重定向到主屏幕。我必须重新加载页面才能显示主屏幕,否则我只会被困在注册表单中。我一直在篡改代码,让我惊讶的是,当我删除await signup(email, password)时,history.push的工作原理与预期一样。我怀疑这种行为与firebase的注册功能有关,但我不知道这是什么。有人能解释一下吗?

代码
这是我的注册表单代码:

import { useState } from "react";
import { useHistory } from "react-router-dom";
import { useAuth } from "../contexts/AuthContext";
function Signup() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [cfPassword, setCfPassword] = useState("");
const {signup} = useAuth();
const history = useHistory();
async function handleSubmit(e) {
e.preventDefault();
if (password !== cfPassword) {
console.log("Passwords do not match!");
return;
}
try {
await signup(email, password);
history.push("/"); // Problematic code is here. This works fine when I remove the previous line.
} catch (error) {
console.log(error.message);
}
}

return (
<div>
<h1>Create an account</h1>
<form onSubmit={handleSubmit}>
<input type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="Email"/>
<input type="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="Password"/>
<input type="password" value={cfPassword} onChange={(e) => setCfPassword(e.target.value)} placeholder="Confirm Password"/>
<input type="submit" value="Sign up"/>
</form>
</div>
);
}
export default Signup;

包含身份验证上下文和注册相关功能的代码:

import { auth } from "../firebase";
import { useState, useEffect, useContext, createContext } from "react";
import { createUserWithEmailAndPassword } from "firebase/auth";
const AuthContext = createContext();
export function useAuth() {
return useContext(AuthContext);
}
export function AuthProvider({children}) {
const [currentUser, setCurrentUser] = useState();
const [isLoading, setIsLoading] = useState(true);
function signup(email, password) {
return createUserWithEmailAndPassword(auth, email, password);
}
const value = {
currentUser,
signup
}
useEffect(() => {
const unsubscriber = onAuthStateChanged(auth, (user) => {
setIsLoading(true);
setCurrentUser(user);
setIsLoading(false);
});
return unsubscriber;
}, []);
return (
<AuthContext.Provider value={value}>
{!isLoading && children}
</AuthContext.Provider>
);
}

我的App.jsx组件包含路由器。

import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import { AuthProvider } from './contexts/AuthContext';
import Home from './pages/mains/home/Home';
import Signup from './components/Signup';
function App() {
return (
<AuthProvider>
<Router>
<Switch>
<Route path="/" exact component={Home} />
<Route path="/signup" component={Signup} />
</Switch>
</Router>
</AuthProvider>
)
}
export default App;

因此,在测试和修改代码后,我发现问题出在AuthProvideruseEffect中的setIsLoading(true)中。去掉那条线,一切都像一个符咒。希望这能帮助遇到同样问题的人。:(

问题

是的,我现在看到了使用isLoading状态有条件地呈现整个应用程序的问题所在。创建新用户后,似乎会调用onAuthStateChanged处理程序并重新加载当前用户。将isLoading状态切换为true时,将卸载AuthContext的子级。

export function AuthProvider({ children }) {
const [currentUser, setCurrentUser] = useState();
const [isLoading, setIsLoading] = useState(true);
function signup(email, password) {
return createUserWithEmailAndPassword(auth, email, password);
}
const value = {
currentUser,
signup
}
useEffect(() => {
const unsubscriber = onAuthStateChanged(auth, (user) => {
setIsLoading(true); // <-- setting true
setCurrentUser(user);
setIsLoading(false);
});
return unsubscriber;
}, []);
return (
<AuthContext.Provider value={value}>
{!isLoading && children} // <-- unmounts children
</AuthContext.Provider>
);
}

解决方案

一个更实用的解决方案是将isLoading状态添加到上下文值中,并创建一个受保护的路由组件,该组件处理有条件呈现null或加载指示符,或路由组件或重定向以登录

示例:

export function AuthProvider({ children }) {
const [currentUser, setCurrentUser] = useState();
const [isLoading, setIsLoading] = useState(true);
function signup(email, password) {
return createUserWithEmailAndPassword(auth, email, password);
}
useEffect(() => {
const unsubscriber = onAuthStateChanged(auth, (user) => {
setIsLoading(true);
setCurrentUser(user);
setIsLoading(false);
});
return unsubscriber;
}, []);
const value = {
currentUser,
isLoading,
signup
}
return (
<AuthContext.Provider value={value}>
{children} // <-- keep children mounted!
</AuthContext.Provider>
);
}

const ProtectedRoute = props => {
const location = useLocation();
const { currentUser, isLoading } = useAuth();
if (isLoading) {
return null;
}
return currentUser
? <Route {...props} />
: (
<Redirect
to={{
pathname: "/login",
state: { from: location } // <-- used to redirect back after auth
}}
/>
);
};

function App() {
return (
<AuthProvider>
<Router>
<Switch>
<ProtectedRoute path="/somethingToProtect" component={<Private />} />
<Route path="/login" component={Login} />
<Route path="/signup" component={Signup} />
<Route path="/" component={Home} />
</Switch>
</Router>
</AuthProvider>
)
}

最新更新