使用 react-router-dom 重新加载页面时出现问题
Problem when reloading page using react-router-dom
我设法创建了一条私人路线,并使用 react-router-dom
导航到不同的页面。然而,当我导航到一个页面并重新加载它时,它首先会转到 /login
半秒钟,然后正确地重新加载页面。如何防止这种不良行为并改进路由?
这是我的路线:
<Router>
<Route
path="/"
component={() =>
!auth ? <Redirect to="/login" /> : <Redirect to={path} />
}
/>
<Route exact path="/home" component={Home} />
<Route exact path="/dashboard" component={Dashboard} />
<Route exact path="/login" component={RedirectPage} />
</Router>
这是完整的组件:
import {
Route,
BrowserRouter as Router,
Link,
Redirect,
} from "react-router-dom";
import { Container, Button } from "@material-ui/core/";
import Login from "./Login";
import { useContext,useState } from "react";
import { UserContext } from "../App";
import { signOut } from "../Storage/Auth";
const Routes = () => {
const { auth, setAuth, logging } = useContext(UserContext);
const [path,setPath] = useState("/home")
const handleSignOut = () => {
signOut(setAuth);
console.log("Auth", auth);
};
const Home = () => {
console.log("Home");
return (
<Container>
<h1>Welcome</h1>
<Link to="/">
<Button onClick={handleSignOut}> Log Out</Button>
</Link>
<Link to="/dashboard">
<Button> Dash</Button>
</Link>
</Container>
);
};
const Dashboard = () => {
setPath("/dashboard")
console.log("Dash");
return (
<Container>
<Link to="/home">
<Button> HOME</Button>
</Link>
<h1>Dashboard</h1>
</Container>
);
};
const RedirectPage = () => {
if (!logging) {
return <div></div>;
} else {
return <Login />;
}
};
return (
<Router>
<Route
path="/"
component={() =>
!auth ? <Redirect to="/login" /> : <Redirect to={path} />
}
/>
<Route exact path="/home" component={Home} />
<Route exact path="/dashboard" component={Dashboard} />
<Route exact path="/login" component={RedirectPage} />
</Router>
);
};
export { Routes };
这是我的 Login
组件。
import { useState, useContext } from "react";
import {
Button,
Card,
Container,
Typography,
Box,
TextField,
} from "@material-ui/core/";
import { useHistory} from "react-router-dom";
import { signIn } from "../Storage/Auth";
import { UserContext } from "../App";
const Login = () => {
const [mail, setMail] = useState<string>("");
const [password, setPassword] = useState<string>("");
const { user, setUser } = useContext(UserContext);
const handleSignIn = async (m: string, p: string) => {
await signIn(m, p).then((e) => {
console.log("USERID", e, user);
setUser(e);
});
};
const history = useHistory();
const handleEnter = () => {
history.push("/home");
};
const handleOnKey = (e: any) => {
if (e.key === "Enter") {
e.preventDefault();
handleSignIn(mail, password);
handleEnter();
}
};
return (
<Card className="Card" raised={true}>
<Container className="Input">
<Typography className="Sign-in" paragraph={true} variant="inherit">
Sign in
</Typography>
<Box
className="Box"
borderColor="error.main"
border={2}
borderRadius="borderRadius"
>
<Container>
<TextField
fullWidth={true}
placeholder=" email"
value={mail}
onChange={(e) => {
setMail(e.target.value);
}}
onKeyDown={(e) => {
handleOnKey(e);
}}
/>
</Container>
</Box>
</Container>
<Container className="Input">
<Box
className="Box"
borderColor="error.main"
borderRadius="borderRadius"
border={2}
>
<Container>
<TextField
fullWidth={true}
placeholder=" password"
value={password}
onChange={(e) => {
setPassword(e.target.value);
}}
type="password"
onKeyDown={(e) => {
handleOnKey(e);
}}
/>
</Container>
</Box>
<h1> </h1>
<Button
onClick={() => {
handleSignIn(mail, password);
}}
fullWidth={true}
color="primary"
variant="contained"
type="submit"
>
Sign In{" "}
</Button>
<h1> </h1>
<Box className="Sign-in">
<Button size="small"> Register </Button>
</Box>
<h1> </h1>
</Container>
</Card>
);
};
export default Login;
这是 App
组件:
import { useEffect } from "react";
import { Routes } from "./Routing/Routes";
import "./App.css";
import { Container } from "@material-ui/core/";
import initFirebase from "./Storage/Secret";
import { useState, createContext } from "react";
import { onAuthChange } from "./Storage/Auth";
export const UserContext = createContext<any>(null);
function App() {
const [user, setUser] = useState(null);
const [auth, setAuth] = useState<string | null>("");
const [logging, setLogging] = useState(null)
useEffect(() => {
initFirebase();
}, []);
useEffect(() => {
onAuthChange(setAuth,setLogging);
}, [auth]);
return (
<UserContext.Provider value={{ user, setUser, auth,setAuth,logging }}>
<div className="App">
<Container>
<Routes />
</Container>
</div>
</UserContext.Provider>
);
}
export default App;
此外,这里是 auth
逻辑:
import firebase from "firebase/app";
import "firebase/auth";
const auth = () => firebase.auth();
const signIn = async (email, password) => {
await auth()
.signInWithEmailAndPassword(email, password)
.then((userCredential) => {
var user = userCredential.user;
console.log("USER", user);
return user.uid;
})
.catch((error) => {
var errorCode = error.code;
var errorMessage = error.message;
alert(errorCode, errorMessage);
return null;
});
};
const onAuthChange = (setState, setLoading) => {
auth().onAuthStateChanged((u) => {
if (!u) {
console.log(u);
setLoading(true);
} else {
setState(u);
setLoading(false);
}
});
};
const signOut = (setState) => {
auth()
.signOut()
.then(function () {
console.log("LOGGED OUT");
})
.catch(function (error) {
console.log("ERROR LOGGING OUT");
});
setState(null);
};
export { signIn, signOut, onAuthChange }
最后,完整代码在https://gitlab.com/programandoconro/adminkanjicreator
如有任何建议,我们将不胜感激。
我建议尽早进行身份验证检查。所以像这样的东西,这样路由本身只有在 auth 中有东西时才会被渲染。我认为您的示例还缺少经常有用的 Switch 语句。
<Router>
{!auth ? (
<Switch>
<Route exact path="/login" component={RedirectPage} />
</Switch>
) : (
<Switch>
<Route exact path="/home" component={Home} />
<Route exact path="/dashboard" component={Dashboard} />
</Switch>
)}
</Router>
通常您需要某种“正在加载”或“不确定”状态来表示既未经过身份验证也未经过身份验证。您可以使用这第三个“状态”来保持 UI,然后再根据身份验证以一种或另一种方式呈现任何内容。
由于您的 auth
逻辑解析为布尔值 true
|false
.
const onAuthChange = (setState, setLoading) => {
auth().onAuthStateChanged((u) => {
if (!u) {
console.log(u);
setLoading(true);
} else {
setState(u);
setLoading(false);
}
});
};
您可以利用初始 auth
状态不是这两种状态的事实。我建议使用 null
.
const [auth, setAuth] = useState<string | null>(null);
使用 auth
状态呈现 Route
时,您可以在决定重定向之前尽早将逻辑扩充到 return。
<Route
path="/"
render={() => {
if (auth === null) return null;
return <Redirect to={auth ? path : "/login" />;
}}
/>
请注意,我还切换到了 render
道具,component
道具用于附加实际的 React 组件。这些处理方式略有不同。您可以阅读有关路由渲染方法差异的信息 here.
完整的路由器示例:
<Router>
<Switch>
<Route path="/home" component={Home} />
<Route path="/dashboard" component={Dashboard} />
<Route path="/login" component={RedirectPage} />
<Route
path="/"
render={() => {
if (auth === null) return null;
return <Redirect to={auth ? path : "/login" />;
}}
/>
</Switch>
</Router>
请注意,我还包含了 Switch
组件并重新排序了路线,以便在较不具体的路径之前列出更具体的路径。这允许您从所有路由中删除不必要的 exact
属性,因为 Switch
只渲染路由( 相对于 Router
所做的 )。
我终于设法解决了这个问题。现在重新加载工作完美,安全性已按例外情况实施。这是我的最终 Router
:
<Router>
<Route
path="/"
render={() =>
logging ? <Redirect to={"/login"} /> : <Redirect to={path} />
}
/>
<Route exact path="/" render={() => auth && <Home />} />
<Route exact path="/dashboard" render={() => auth && <Dashboard />} />
<Route exact path="/login" component={Login} />
</Router>
这是组件现在的样子。
import {
Route,
BrowserRouter as Router,
Link,
Redirect
} from "react-router-dom";
import { Container, Button } from "@material-ui/core/";
import Login from "./Login";
import { useContext, useState, useEffect } from "react";
import { UserContext } from "../App";
import { signOut } from "../Storage/Auth";
const Routes = () => {
const { auth, setAuth, logging } = useContext(UserContext);
const handleSignOut = () => {
signOut(setAuth);
console.log("Auth", auth);
};
const pathname = window.location.pathname;
const [path, setPath] = useState(pathname);
useEffect(() => {
console.log(path);
path === "/login" && setPath("/");
path !== "/" && path !== "/dashboard" && setPath("/");
}, [auth]);
const Home = () => {
console.log("Home");
return (
<Container>
<h1>Welcome</h1>
<Link to="/">
<Button onClick={handleSignOut}> Log Out</Button>
</Link>
<Link to="/dashboard">
<Button> Dash</Button>
</Link>
</Container>
);
};
const Dashboard = () => {
console.log("Dash");
return (
<Container>
<Link to="/">
<Button> HOME</Button>
</Link>
<h1>Dashboard</h1>
</Container>
);
};
return (
<Router>
<Route
path="/"
render={() =>
logging ? <Redirect to={"/login"} /> : <Redirect to={path} />
}
/>
<Route exact path="/" render={() => auth && <Home />} />
<Route exact path="/dashboard" render={() => auth && <Dashboard />} />
<Route exact path="/login" component={Login} />
</Router>
);
};
export { Routes };
感谢@Richard 和@Drew 的支持。
我设法创建了一条私人路线,并使用 react-router-dom
导航到不同的页面。然而,当我导航到一个页面并重新加载它时,它首先会转到 /login
半秒钟,然后正确地重新加载页面。如何防止这种不良行为并改进路由?
这是我的路线:
<Router>
<Route
path="/"
component={() =>
!auth ? <Redirect to="/login" /> : <Redirect to={path} />
}
/>
<Route exact path="/home" component={Home} />
<Route exact path="/dashboard" component={Dashboard} />
<Route exact path="/login" component={RedirectPage} />
</Router>
这是完整的组件:
import {
Route,
BrowserRouter as Router,
Link,
Redirect,
} from "react-router-dom";
import { Container, Button } from "@material-ui/core/";
import Login from "./Login";
import { useContext,useState } from "react";
import { UserContext } from "../App";
import { signOut } from "../Storage/Auth";
const Routes = () => {
const { auth, setAuth, logging } = useContext(UserContext);
const [path,setPath] = useState("/home")
const handleSignOut = () => {
signOut(setAuth);
console.log("Auth", auth);
};
const Home = () => {
console.log("Home");
return (
<Container>
<h1>Welcome</h1>
<Link to="/">
<Button onClick={handleSignOut}> Log Out</Button>
</Link>
<Link to="/dashboard">
<Button> Dash</Button>
</Link>
</Container>
);
};
const Dashboard = () => {
setPath("/dashboard")
console.log("Dash");
return (
<Container>
<Link to="/home">
<Button> HOME</Button>
</Link>
<h1>Dashboard</h1>
</Container>
);
};
const RedirectPage = () => {
if (!logging) {
return <div></div>;
} else {
return <Login />;
}
};
return (
<Router>
<Route
path="/"
component={() =>
!auth ? <Redirect to="/login" /> : <Redirect to={path} />
}
/>
<Route exact path="/home" component={Home} />
<Route exact path="/dashboard" component={Dashboard} />
<Route exact path="/login" component={RedirectPage} />
</Router>
);
};
export { Routes };
这是我的 Login
组件。
import { useState, useContext } from "react";
import {
Button,
Card,
Container,
Typography,
Box,
TextField,
} from "@material-ui/core/";
import { useHistory} from "react-router-dom";
import { signIn } from "../Storage/Auth";
import { UserContext } from "../App";
const Login = () => {
const [mail, setMail] = useState<string>("");
const [password, setPassword] = useState<string>("");
const { user, setUser } = useContext(UserContext);
const handleSignIn = async (m: string, p: string) => {
await signIn(m, p).then((e) => {
console.log("USERID", e, user);
setUser(e);
});
};
const history = useHistory();
const handleEnter = () => {
history.push("/home");
};
const handleOnKey = (e: any) => {
if (e.key === "Enter") {
e.preventDefault();
handleSignIn(mail, password);
handleEnter();
}
};
return (
<Card className="Card" raised={true}>
<Container className="Input">
<Typography className="Sign-in" paragraph={true} variant="inherit">
Sign in
</Typography>
<Box
className="Box"
borderColor="error.main"
border={2}
borderRadius="borderRadius"
>
<Container>
<TextField
fullWidth={true}
placeholder=" email"
value={mail}
onChange={(e) => {
setMail(e.target.value);
}}
onKeyDown={(e) => {
handleOnKey(e);
}}
/>
</Container>
</Box>
</Container>
<Container className="Input">
<Box
className="Box"
borderColor="error.main"
borderRadius="borderRadius"
border={2}
>
<Container>
<TextField
fullWidth={true}
placeholder=" password"
value={password}
onChange={(e) => {
setPassword(e.target.value);
}}
type="password"
onKeyDown={(e) => {
handleOnKey(e);
}}
/>
</Container>
</Box>
<h1> </h1>
<Button
onClick={() => {
handleSignIn(mail, password);
}}
fullWidth={true}
color="primary"
variant="contained"
type="submit"
>
Sign In{" "}
</Button>
<h1> </h1>
<Box className="Sign-in">
<Button size="small"> Register </Button>
</Box>
<h1> </h1>
</Container>
</Card>
);
};
export default Login;
这是 App
组件:
import { useEffect } from "react";
import { Routes } from "./Routing/Routes";
import "./App.css";
import { Container } from "@material-ui/core/";
import initFirebase from "./Storage/Secret";
import { useState, createContext } from "react";
import { onAuthChange } from "./Storage/Auth";
export const UserContext = createContext<any>(null);
function App() {
const [user, setUser] = useState(null);
const [auth, setAuth] = useState<string | null>("");
const [logging, setLogging] = useState(null)
useEffect(() => {
initFirebase();
}, []);
useEffect(() => {
onAuthChange(setAuth,setLogging);
}, [auth]);
return (
<UserContext.Provider value={{ user, setUser, auth,setAuth,logging }}>
<div className="App">
<Container>
<Routes />
</Container>
</div>
</UserContext.Provider>
);
}
export default App;
此外,这里是 auth
逻辑:
import firebase from "firebase/app";
import "firebase/auth";
const auth = () => firebase.auth();
const signIn = async (email, password) => {
await auth()
.signInWithEmailAndPassword(email, password)
.then((userCredential) => {
var user = userCredential.user;
console.log("USER", user);
return user.uid;
})
.catch((error) => {
var errorCode = error.code;
var errorMessage = error.message;
alert(errorCode, errorMessage);
return null;
});
};
const onAuthChange = (setState, setLoading) => {
auth().onAuthStateChanged((u) => {
if (!u) {
console.log(u);
setLoading(true);
} else {
setState(u);
setLoading(false);
}
});
};
const signOut = (setState) => {
auth()
.signOut()
.then(function () {
console.log("LOGGED OUT");
})
.catch(function (error) {
console.log("ERROR LOGGING OUT");
});
setState(null);
};
export { signIn, signOut, onAuthChange }
最后,完整代码在https://gitlab.com/programandoconro/adminkanjicreator
如有任何建议,我们将不胜感激。
我建议尽早进行身份验证检查。所以像这样的东西,这样路由本身只有在 auth 中有东西时才会被渲染。我认为您的示例还缺少经常有用的 Switch 语句。
<Router>
{!auth ? (
<Switch>
<Route exact path="/login" component={RedirectPage} />
</Switch>
) : (
<Switch>
<Route exact path="/home" component={Home} />
<Route exact path="/dashboard" component={Dashboard} />
</Switch>
)}
</Router>
通常您需要某种“正在加载”或“不确定”状态来表示既未经过身份验证也未经过身份验证。您可以使用这第三个“状态”来保持 UI,然后再根据身份验证以一种或另一种方式呈现任何内容。
由于您的 auth
逻辑解析为布尔值 true
|false
.
const onAuthChange = (setState, setLoading) => {
auth().onAuthStateChanged((u) => {
if (!u) {
console.log(u);
setLoading(true);
} else {
setState(u);
setLoading(false);
}
});
};
您可以利用初始 auth
状态不是这两种状态的事实。我建议使用 null
.
const [auth, setAuth] = useState<string | null>(null);
使用 auth
状态呈现 Route
时,您可以在决定重定向之前尽早将逻辑扩充到 return。
<Route
path="/"
render={() => {
if (auth === null) return null;
return <Redirect to={auth ? path : "/login" />;
}}
/>
请注意,我还切换到了 render
道具,component
道具用于附加实际的 React 组件。这些处理方式略有不同。您可以阅读有关路由渲染方法差异的信息 here.
完整的路由器示例:
<Router>
<Switch>
<Route path="/home" component={Home} />
<Route path="/dashboard" component={Dashboard} />
<Route path="/login" component={RedirectPage} />
<Route
path="/"
render={() => {
if (auth === null) return null;
return <Redirect to={auth ? path : "/login" />;
}}
/>
</Switch>
</Router>
请注意,我还包含了 Switch
组件并重新排序了路线,以便在较不具体的路径之前列出更具体的路径。这允许您从所有路由中删除不必要的 exact
属性,因为 Switch
只渲染路由( 相对于 Router
所做的 )。
我终于设法解决了这个问题。现在重新加载工作完美,安全性已按例外情况实施。这是我的最终 Router
:
<Router>
<Route
path="/"
render={() =>
logging ? <Redirect to={"/login"} /> : <Redirect to={path} />
}
/>
<Route exact path="/" render={() => auth && <Home />} />
<Route exact path="/dashboard" render={() => auth && <Dashboard />} />
<Route exact path="/login" component={Login} />
</Router>
这是组件现在的样子。
import {
Route,
BrowserRouter as Router,
Link,
Redirect
} from "react-router-dom";
import { Container, Button } from "@material-ui/core/";
import Login from "./Login";
import { useContext, useState, useEffect } from "react";
import { UserContext } from "../App";
import { signOut } from "../Storage/Auth";
const Routes = () => {
const { auth, setAuth, logging } = useContext(UserContext);
const handleSignOut = () => {
signOut(setAuth);
console.log("Auth", auth);
};
const pathname = window.location.pathname;
const [path, setPath] = useState(pathname);
useEffect(() => {
console.log(path);
path === "/login" && setPath("/");
path !== "/" && path !== "/dashboard" && setPath("/");
}, [auth]);
const Home = () => {
console.log("Home");
return (
<Container>
<h1>Welcome</h1>
<Link to="/">
<Button onClick={handleSignOut}> Log Out</Button>
</Link>
<Link to="/dashboard">
<Button> Dash</Button>
</Link>
</Container>
);
};
const Dashboard = () => {
console.log("Dash");
return (
<Container>
<Link to="/">
<Button> HOME</Button>
</Link>
<h1>Dashboard</h1>
</Container>
);
};
return (
<Router>
<Route
path="/"
render={() =>
logging ? <Redirect to={"/login"} /> : <Redirect to={path} />
}
/>
<Route exact path="/" render={() => auth && <Home />} />
<Route exact path="/dashboard" render={() => auth && <Dashboard />} />
<Route exact path="/login" component={Login} />
</Router>
);
};
export { Routes };
感谢@Richard 和@Drew 的支持。