成功登录后,我尝试
navigate('/')
,但它无法完全正常工作。我可以看到 url 更新,但 <Home />
(可手动导航)组件未呈现。
不确定此信息是否相关,但这是整个流程。下面是我的
handleSignIn
函数,它正在调用从 handleAuth
钩子导出的 useAuth
。
const { isAuthenticating, error, handleAuth } = useAuth()
const handleSignIn = async (e) => {
e.preventDefault()
const success = await handleAuth(signin, emailRef.current.value, passwordRef.current.value)
if (success) {
navigate('/')
}
}
这个钩子然后发送一个 thunk
来自 useAuth.js:
const { isAuthenticating, error } = useSelector(state => state.user)
const handleAuth = async (authType, email, password) => {
try {
const action = await dispatch(authType({ email, password }))
if (authType.fulfilled.match(action)) {
console.log('worked')
return true;
}
} catch (err) {
console.log(err)
return false;
}
}
signin
重击:
export const signin = createAsyncThunk(
'user/signin',
async({ email, password }, { rejectWithValue }) => {
try {
const response = await signInWithEmailAndPassword(auth, email, password)
const accessToken = await response.user.getIdToken()
return {
uid: response.user.uid,
email: response.user.email,
accessToken
}
} catch (error) {
return rejectWithValue(error.message)
}
}
)
然后我在 user-slice.js 中使用
extraReducers
来处理加载和错误状态并更新 redux 存储。这个(isAuthenticating
,error
)是从useAuth
钩子上拉出来的。
const initialState = {
user: null,
error: {
type: null,
message: null
},
isAuthenticating: false,
}
extraReducers: (builder) => {
builder
.addCase(signin.pending, (state) => {
state.isAuthenticating = true;
state.error = initialState.error;
})
.addCase(signin.fulfilled, (state, action) => {
state.user = action.payload;
state.isAuthenticating = false;
localStorage.setItem('accessToken', action.payload.accessToken);
localStorage.setItem('email', action.payload.email);
localStorage.setItem('uid', action.payload.uid);
})
.addCase(signin.rejected, (state, action) => {
state.isAuthenticating = false;
state.error = handleError(action.payload);
});
},
下面是我的路由器路径配置和
PrivateRoute
组件。
const router = createBrowserRouter([
{
path: '/',
element: <RootLayout />,
errorElement: <Error />,
children: [
{
index: true,
element: <PrivateRoute element={<Home />} />,
},
{
path: 'get-started',
element: <LandingLayout />,
children: [
{
index: true,
element: <GetStarted />,
},
{
path: 'signup',
element: <SignUp />
},
]
}
]
}
])
const PrivateRoute = ({ element }) => {
const user = useSelector(state => state.user)
return user ? element : <Navigate to="/get-started" />
}
RootLayout
const RootLayout = () => {
return (
<>
<Nav />
<Outlet />
</>
)
}
我无法找出是什么原因造成的。任何帮助将不胜感激。
看起来问题在于
Private
路由查看的是 整个 state.user
对象,而不仅仅是 state.user.user
值,即 null
或 Firebase 用户对象。
store.js:
const store = configureStore({
reducer: {
user: userSlice.reducer, // <-- state.user
},
});
用户切片.js:
const initialState = {
user: null, // <-- store.user.user
error: {
type: null,
message: null,
},
isAuthenticating: false,
};
PrivateRoute.js
const PrivateRoute = ({ element }) => {
const user = useSelector((state) => state.user);
console.log(user); // <-- only state.user, e.g. { user , error, isAuthenticating }
return user ? element : <Navigate to="/get-started" />;
};
在
PrivateRoute
user
中,always 是一个已定义的对象,因此 element
或 Home
组件将 always 被渲染。
更新
PrivateRoute
以引用 nested state.user.user
属性来了解当前是否有任何经过身份验证的用户。
const PrivateRoute = ({ element }) => {
const { user } = useSelector((state) => state.user);
return user ? element : <Navigate to="/get-started" />;
};
我建议还集成
isAuthenticating
状态以在任何身份验证检查正在进行时显示加载指示器。
const PrivateRoute = ({ element }) => {
const { isAuthenticating, user } = useSelector((state) => state.user);
if (isAuthenticating) {
return <LoadingSpinner />;
}
return user ? element : <Navigate to="/get-started" />;
};
通常建议将
PrivateRoute
组件实现为布局路由组件,这样它就能够保护整组路由而不是一次保护一个路由。
const PrivateRoute = () => {
const { isAuthenticating, user } = useSelector((state) => state.user);
if (isAuthenticating) {
return <LoadingSpinner />;
}
return user ? <Outlet /> : <Navigate to="/get-started" />;
};
应用程序.js
const router = createBrowserRouter([
{
path: "/",
element: <RootLayout />,
children: [
{
element: <PrivateRoute />,
children: [
{ index: true, element: <Home /> },
// other protected routes
],
}
{
path: "get-started",
element: <AuthLayout />,
children: [
{ path: "signup", element: <SignUp /> },
],
},
],
},
]);