英文:
useNavigate() not redirecting to '/protected'
问题
以下是您要翻译的代码部分:
LoginForm.js:
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useMutation } from 'react-query';
import api from './api';
function LoginForm() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const navigate = useNavigate();
const handleLogin = async () => {
const response = await api.post('/authenticate', { email, password });
return response.data;
};
const { mutate, isLoading } = useMutation(handleLogin, {
onSuccess: (data) => {
localStorage.setItem('token', data.token);
console.log(data.token);
navigate('/protected');
},
onError: (error) => {
setError(error.response.data);
},
});
const handleSubmit = (e) => {
mutate();
e.preventDefault();
};
return (
<form onSubmit={handleSubmit}>
<label>
Email:
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</label>
<label>
Password:
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</label>
{error && <div>{error}</div>}
<button type="submit" disabled={isLoading}>
{isLoading ? 'Logging in...' : 'Login'}
</button>
</form>
);
}
export default LoginForm;
App.js:
import {
BrowserRouter as Router,
Routes,
Route,
Navigate
} from 'react-router-dom';
import { useQuery } from 'react-query';
import LoginForm from './LoginForm';
import ProtectedResource from './ProtectedResource';
import api from './api';
function App() {
const { isLoading, error, data: isLoggedIn } = useQuery('isLoggedIn', async () => {
const token = localStorage.getItem('token');
if (token) {
try {
await api.get('/resource', {
headers: {
Authorization: `Bearer ${token}`,
},
});
return true;
} catch (error) {
if (error.response.status === 401) {
localStorage.removeItem('token');
}
return false;
}
} else {
return false;
}
});
if (isLoading) return <p>Loading...</p>;
if (error) return <p>An error occurred: {error.message}</p>;
return (
<Router>
<Routes>
<Route
path="/"
element={isLoggedIn ? <Navigate to="/protected" /> : <LoginForm />}
/>
<Route
path="/protected"
element={isLoggedIn ? <ProtectedResource /> : <Navigate to="/" />}
/>
</Routes>
</Router>
);
}
export default App;
英文:
The API is working fine, it returns the token that then is saved in my local memory but it doesn't redirect for some reason. If I refresh my window it takes me straight to the "/protected"
page.
LoginForm.js
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useMutation } from 'react-query';
import api from './api';
function LoginForm() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const navigate = useNavigate();
const handleLogin = async () => {
const response = await api.post('/authenticate', { email, password });
return response.data;
};
const { mutate, isLoading } = useMutation(handleLogin, {
onSuccess: (data) => {
localStorage.setItem('token', data.token);
console.log(data.token);
navigate('/protected');
},
onError: (error) => {
setError(error.response.data);
},
});
const handleSubmit = (e) => {
mutate();
e.preventDefault();
};
return (
<form onSubmit={handleSubmit}>
<label>
Email:
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</label>
<label>
Password:
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</label>
{error && <div>{error}</div>}
<button type="submit" disabled={isLoading}>
{isLoading ? 'Logging in...' : 'Login'}
</button>
</form>
);
}
export default LoginForm;
Here is my App.js
as the protected page does not matter as it only has an <h1></h1>
. My API does not apply either as I get the required token without an issue. The only issue is redirecting. I did force window reload but that is a work-around that I bet is not safe?
import {
BrowserRouter as Router,
Routes,
Route,
Navigate
} from 'react-router-dom';
import { useQuery } from 'react-query';
import LoginForm from './LoginForm';
import ProtectedResource from './ProtectedResource';
import api from './api';
function App() {
const { isLoading, error, data: isLoggedIn } = useQuery('isLoggedIn', async () => {
const token = localStorage.getItem('token');
if (token) {
try {
await api.get('/resource', {
headers: {
Authorization: `Bearer ${token}`,
},
});
return true;
} catch (error) {
if (error.response.status === 401) {
localStorage.removeItem('token');
}
return false;
}
} else {
return false;
}
});
if (isLoading) return <p>Loading...</p>;
if (error) return <p>An error occurred: {error.message}</p>;
return (
<Router>
<Routes>
<Route
path="/"
element={isLoggedIn ? <Navigate to="/protected" /> : <LoginForm />}
/>
<Route
path="/protected"
element={isLoggedIn ? <ProtectedResource /> : <Navigate to="/" />}
/>
</Routes>
</Router>
);
}
export default App;
答案1
得分: 2
以下是您要翻译的内容:
代码中使用localStorage
作为数据源,但更新localStorage
不会触发App
React组件重新渲染,以显示新的身份验证值,以便正确保护路由或在必要时重定向。令牌值存储在localStorage
中,导航到"/protected"
已生效,但由于App
未重新渲染,useQuery
钩子未重新运行以检查用户的令牌值。
重构代码以向App
添加状态,该状态在成功登录和注销时更新。这将触发App
重新渲染并重新运行useQuery
以检查存储的token
值。
示例:
export default function App() {
const [token, setToken] = React.useState(); // <-- 状态
const { isLoading, error, data: isLoggedIn } = useQuery("isLoggedIn", async () => {
const token = localStorage.getItem("token");
if (token) {
try {
await api.get("/resource", {
headers: {
Authorization: `Bearer ${token}`
}
});
return true;
} catch (error) {
if (error.response.status === 401) {
localStorage.removeItem("token");
}
return false;
}
} else {
return false;
}
});
if (isLoading) return <p>Loading...</p>;
if (error) return <p>An error occurred: {error.message}</p>;
return (
...
<Routes>
<Route
path="/"
element={
isLoggedIn ? (
<Navigate to="/protected" />
) : (
<LoginForm setToken={setToken} /> // <-- 传递设置器
)
}
/>
<Route
path="/protected"
element={isLoggedIn ? <ProtectedResource /> : <Navigate to="/" />}
/>
</Routes>
...
);
}
function LoginForm({ setToken }) { // <-- 使用setToken回调
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState("");
const navigate = useNavigate();
const handleLogin = async () => {
const response = await api.post("/authenticate", { email, password });
return response.data;
};
const { mutate, isLoading } = useMutation(handleLogin, {
onSuccess: (data) => {
localStorage.setItem("token", data.token);
setToken(data.token); // <-- 更新令牌状态
navigate("/protected");
},
onError: (error) => {
setError(error.response.data);
}
});
const handleSubmit = (e) => {
mutate();
e.preventDefault();
};
return (
<form onSubmit={handleSubmit}>
...
</form>
);
}
演示链接:
英文:
The code is using localStorage
as the source of truth, but updating localStorage doesn't trigger the App
React component to rerender with the new authentication value in order to correctly protect routes or redirect when necessary. The token value is stored in localStorage and the navigation action to "/protected"
is effected, but because App
didn't rerender, the useQuery
hook didn't rerun to check the user's token value.
Refactor the code to add state to App
that is updated upon successful log in & out. This will trigger App
to rerender and rerun useQuery
and check the stored token
value.
Example:
export default function App() {
const [token, setToken] = React.useState(); // <-- state
const { isLoading, error, data: isLoggedIn } = useQuery("isLoggedIn", async () => {
const token = localStorage.getItem("token");
if (token) {
try {
await api.get("/resource", {
headers: {
Authorization: `Bearer ${token}`
}
});
return true;
} catch (error) {
if (error.response.status === 401) {
localStorage.removeItem("token");
}
return false;
}
} else {
return false;
}
});
if (isLoading) return <p>Loading...</p>;
if (error) return <p>An error occurred: {error.message}</p>;
return (
...
<Routes>
<Route
path="/"
element={
isLoggedIn ? (
<Navigate to="/protected" />
) : (
<LoginForm setToken={setToken} /> // <-- pass setter
)
}
/>
<Route
path="/protected"
element={isLoggedIn ? <ProtectedResource /> : <Navigate to="/" />}
/>
</Routes>
...
);
}
function LoginForm({ setToken }) { // <-- consume setToken callback
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState("");
const navigate = useNavigate();
const handleLogin = async () => {
const response = await api.post("/authenticate", { email, password });
return response.data;
};
const { mutate, isLoading } = useMutation(handleLogin, {
onSuccess: (data) => {
localStorage.setItem("token", data.token);
setToken(data.token); // <-- update token state
navigate("/protected");
},
onError: (error) => {
setError(error.response.data);
}
});
const handleSubmit = (e) => {
mutate();
e.preventDefault();
};
return (
<form onSubmit={handleSubmit}>
...
</form>
);
}
Demo
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论