提问人:Bidou 提问时间:8/6/2023 最后编辑:Drew ReeseBidou 更新时间:8/7/2023 访问量:492
我无法发送我的授权令牌来使用 RTK Query 检索配置文件
I don't manage to send my authorization token to retrieve a profile with RTK Query
问:
我已经做了 3 天了,我需要一些帮助。
我正在从事我所遵循的课程中的一个项目,其中必须使用 React + Redux 工具包 (RTK)。
我找到了 RTK Query,我设法通过 POST 向我的 API 发送登录名 + 密码,并从我的 Express 服务器获得正确的响应(后端已经完成并给了我):登录不正确,密码不正确,登录成功。
之后,我可以正确地控制台记录我的令牌。
但是,我需要执行另一个 POST 来检索配置文件(名字、姓氏、用户名),为此,我需要将收到的令牌放在我的 POST 标头中。这就是我被困住的地方。
我不知道如何调试这一切。我花了最后两天的时间观看/阅读教程、文档,我什至问过 ChatGPT,但无济于事。我无法用令牌填写我的 POST 标头,并且由于我的令牌始终未定义,因此问题必须出在这里:
const token = getState().auth.data.body.token;
我无法弄清楚检索令牌的路径应该是什么。
我很确定答案很简单,而且我错过了一些明显的东西,而且在我眼前,但我没有找到问题。
这是我的 API (localhost:3001/api/v1):
POST 用户/登录
响应正文:(这个有效)
{
"status": 200,
"message": "User successfully logged in",
"body": {
"token": ""
}
}
POST 用户/配置文件
响应正文:(这个我无法检索)
{
"status": 200,
"message": "Successfully got user profile data",
"body": {
"email": "[email protected]",
"firstName": "firstnamE",
"lastName": "lastnamE",
"userName": "Myusername",
"createdAt": "2023-07-18T01:00:34.077Z",
"updatedAt": "2023-08-02T01:17:22.977Z",
"id": "64b5e43291e10972285896bf"
}
}
我不发布用户更新,因为它与此处无关。
这是我的文件:
存储 .js:
import { configureStore } from '@reduxjs/toolkit'
import { bankApi } from './ApiSlice.js'
export const store = configureStore({
reducer: {
// Add the generated reducer as a specific top-level slice
[bankApi.reducerPath]: bankApi.reducer,
},
// Adding the api middleware enables caching, invalidation, polling,
// and other useful features of `rtk-query`.
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(bankApi.middleware),
})
ApiSlice.js:
// Need to use the React-specific entry point to import createApi
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
const apiBaseUrl = 'http://localhost:3001/api/v1';
// Define a service using a base URL and expected endpoints
export const bankApi = createApi({
reducerPath: 'bankApi',
baseQuery: fetchBaseQuery({
baseUrl: apiBaseUrl,
prepareHeaders: (headers, { getState }) => {
console.log('prepareHeaders is called');
const token = getState().auth.data.body.token;
if (token) {
headers.set('Authorization', `Bearer ${token}`);
}
return headers;
},
}),
endpoints: (builder) => ({
auth: builder.mutation({
query: (credentials) => ({
url: '/user/login',
method: 'POST',
body: credentials,
}),
}),
getProfile: builder.mutation({
query: () => ({
url: '/user/profile',
method: 'POST',
}),
}),
updateProfile: builder.query({
query: () => ({
url: '/user/profile',
method: 'PUT',
}),
}),
}),
})
// Export hooks for usage in functional components, which are
// auto-generated based on the defined endpoints
export const {
useAuthMutation,
useGetProfileMutation,
useUpdateProfileQuery
} = bankApi
登录页面.jsx:
import { useState } from 'react';
import { useAuthMutation, useGetProfileMutation } from '../rtk/ApiSlice'
export default function LoginPage(){
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [
login,
{
isLoading: loginIsLoading,
isError: loginIsError,
isSuccess: loginIsSuccess,
data: loginData,
error: loginError
}
] = useAuthMutation();
const [
profile,
{
isError: profileIsError,
error: profileError,
data: profileData
}
] = useGetProfileMutation();
const handleLogin = (e) => {
e.preventDefault();
login({ email, password });
};
const handleProfile = () => {
profile();
};
return (
<div className="main bg-dark">
<section className="sign-in-content">
<i className="fa fa-user-circle sign-in-icon"></i>
<h1>Sign In</h1>
<form
onSubmit={(e) => {
e.preventDefault();
handleLogin();
}}
>
<div className="input-wrapper">
<label>Username</label>
<input
type="text"
id="email"
placeholder=""
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</div>
<div className="input-wrapper">
<label>Password</label>
<input
type="password"
id="password"
placeholder=""
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</div>
<div className="input-remember">
<input type="checkbox" id="remember-me" />
<label>Remember me</label>
</div>
<button
className="sign-in-button"
type="submit"
disabled={loginIsLoading}
>
{loginIsLoading ? 'Signing in...' : 'Sign in'}
</button>
</form>
{loginIsError && <p className='perror'>
{loginError.data.status} {loginError.data.message}
</p>}
{loginIsSuccess && <>
<p className='psuccess'>
{loginData.message} Token: {loginData.body.token}
</p>
<button onClick={handleProfile}>
Get Profile
</button>
</>}
{profileIsError && <p className='perror'>
{profileError.data.status} {profileError.data.message}
</p>}
{profileData && <p className='psuccess'>
{profileData.message} First Name: {profileData.body.firstName} Last Name: {profileData.body.lastName} Surname: {profileData.body.userName}
</p>}
</section>
</div>
);
}
它看起来像什么:
- 电子邮件错误:https://i.snipboard.io/PDRzNv.jpg
- 密码错误:https://i.snipboard.io/vKZI2t.jpg
- 登录成功: https://i.snipboard.io/Jz84H1.jpg
- 单击“获取配置文件”后,标头中缺少令牌:https://i.snipboard.io/RedpYj.jpg
我尝试过:
- 阅读有关 RTK Query 的文档
- 观看 RTK Query 教程
- 尝试检索令牌并通过以下方式进行设置
prepareHeaders
- 尝试在查询中检索令牌
prepareHeaders
getProfile
- 已尝试将令牌传递给
useGetProfileMutation
Loginpage.jsx
- 已尝试将令牌存储在其中并检索它以将其传递给
localStorage
useGetProfileMutation
- 尝试等待登录成功后再调用
useGetProfileMutation
答:
代码中的主要问题是您没有坚持身份验证响应。这就是为什么你的 getState() 方法不能使用 token 的原因。因此,您必须使用 localStorage 或任何持久化库(如 redux-persist)来持久化您的令牌。在这里,我将展示使用 redux-persist 修改后的代码。
创建一个名为“authSlice.js”的切片,如下所示来存储身份验证响应:
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
isLoggedIn: false,
userDetails: {}
};
export const authSlice = createSlice({
name: 'auth',
initialState,
reducers: {
logIn: (state,action) => {
state.isLoggedIn = true
state.userDetails = action.payload
},
logOut: (state) => state = initialState,
},
});
export const { logIn, logOut, isLoggedIn } = authSlice.actions;
export default authSlice.reducer
然后安装 redux-persist 和
存储 .js:
import { configureStore, combineReducers } from '@reduxjs/toolkit'
import { bankApi } from './ApiSlice.js'
import authReducer from './authSlice.js'
import { FLUSH, PAUSE, PERSIST, PURGE, REGISTER, REHYDRATE, persistStore,
persistReducer } from 'redux-persist'
const persistConfig = {
key: 'root',
version: 1,
storage: sessionStorage,
blacklist: [bankApi.reducerPath]
}
const rootReducer = combineReducers({ // added combineReducer since we have now two reducer
auth: authReducer,
[bankApi.reducerPath]: bankApi.reducer,
})
const persistedReducer = persistReducer(persistConfig, rootReducer); // persisted our slice data using redux-persist
export const store = configureStore({
reducer: persistedReducer,
// Adding the api middleware enables caching, invalidation, polling,
// and other useful features of `rtk-query`.
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: { // if you are not using nextj, don't add this object
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER]
}).concat(bankApi.middleware),
})
apiSlice.js:
// Need to use the React-specific entry point to import createApi
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
const apiBaseUrl = 'http://localhost:3001/api/v1';
// Define a service using a base URL and expected endpoints
export const bankApi = createApi({
reducerPath: 'bankApi',
baseQuery: fetchBaseQuery({
baseUrl: apiBaseUrl,
prepareHeaders: (headers, { getState }) => {
console.log('prepareHeaders is called');
const token = getState().auth.userDetails.token; // we are now consuming token from new created authSlice
if (token) {
headers.set('Authorization', `Bearer ${token}`);
}
return headers;
},
}),
endpoints: (builder) => ({
auth: builder.mutation({
query: (credentials) => ({
url: '/user/login',
method: 'POST',
body: credentials,
}),
}),
getProfile: builder.mutation({
query: () => ({
url: '/user/profile',
method: 'POST',
}),
}),
updateProfile: builder.query({
query: () => ({
url: '/user/profile',
method: 'PUT',
}),
}),
}),
})
// Export hooks for usage in functional components, which are
// auto-generated based on the defined endpoints
export const { useAuthMutation, useGetProfileMutation, useUpdateProfileQuery } = bankApi
现在修改登录页面,如下所示:Login.jsx
import { useState } from 'react';
import { useAuthMutation, useGetProfileMutation } from '../rtk/ApiSlice'
import {logIn} from '../rtk/authSlice' // will use to persist auth response
export default function LoginPage(){
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [login, { isLoading: loginIsLoading, isError: loginIsError, isSuccess: loginIsSuccess, data: loginData, error: loginError }] = useAuthMutation();
const [profile, { isError: profileIsError, error: profileError, data: profileData }] = useGetProfileMutation();
const handleLogin = (e) => {
e.preventDefault();
login({ email, password });
};
const handleProfile = () => {
profile();
};
// here we are storing the auth response(token) while loginSuccess is true. And redux persist will automatically persist this for us
// While you wanna logout call the logOut method of authSlice it will handle your logout scenario
useEffect(() => {
if(loginData && loginIsSuccess){
logIn({token:loginData.data.token});
}
},[loginData, loginIsSuccess]); // assuming loginData contains token
return (
<div className="main bg-dark">
<section className="sign-in-content">
<i className="fa fa-user-circle sign-in-icon"></i>
<h1>Sign In</h1>
<form onSubmit={(e) => { e.preventDefault(); handleLogin(); }}>
<div className="input-wrapper">
<label>Username</label>
<input type="text" id="email" placeholder="" value={email} onChange={(e) => setEmail(e.target.value)}/>
</div>
<div className="input-wrapper">
<label>Password</label>
<input type="password" id="password" placeholder="" value={password} onChange={(e) => setPassword(e.target.value)}/>
</div>
<div className="input-remember">
<input type="checkbox" id="remember-me" />
<label>Remember me</label>
</div>
<button className="sign-in-button" type="submit" disabled={loginIsLoading}>{loginIsLoading ? 'Signing in...' : 'Sign in'}</button>
</form>
{loginIsError && <p className='perror'>{loginError.data.status} {loginError.data.message}</p>}
{loginIsSuccess && <><p className='psuccess'>{loginData.message} Token: {loginData.body.token}</p> <button onClick={handleProfile}>Get Profile</button></>}
{profileIsError && <p className='perror'>{profileError.data.status} {profileError.data.message}</p>}
{profileData && <p className='psuccess'>{profileData.message} First Name: {profileData.body.firstName} Last Name: {profileData.body.lastName} Surname: {profileData.body.userName}</p>}
</section>
</div>
);
}
通常,您可能需要在 API 片之外保留一些缓存的查询/变更。创建一个身份验证切片以保存身份验证对象引用。然后,您将能够从查询/突变中调度一个操作,以使用 onQueryStarted 更新身份验证状态,然后可以在基本查询函数中访问该操作,以便使用存储的令牌值设置身份验证标头。
例:
身份验证切片.js
import { createSlice } from '@reduxjs/toolkit';
const initialState = {
token: null,
};
const authSlice = createSlice({
name: "auth",
initialState,
reducers: {
setAuthToken: (state, action) => {
state.token = action.payload;
},
},
});
export const { setAuthToken } = authSlice.actions;
export default authSlice.reducer;
存储 .js
import { configureStore } from '@reduxjs/toolkit';
import { bankApi } from './ApiSlice.js';
import authReducer from './auth.slice.js';
export const store = configureStore({
reducer: {
[bankApi.reducerPath]: bankApi.reducer,
auth: authReducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(bankApi.middleware),
})
api.slice.js
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
import { setAuthToken } from './auth.slice.js';
const apiBaseUrl = 'http://localhost:3001/api/v1';
export const bankApi = createApi({
reducerPath: 'bankApi',
baseQuery: fetchBaseQuery({
baseUrl: apiBaseUrl,
prepareHeaders: (headers, { getState }) => {
console.log('prepareHeaders is called');
const token = getState().auth.token;
if (token) {
headers.set('Authorization', `Bearer ${token}`);
}
return headers;
},
}),
endpoints: (builder) => ({
auth: builder.mutation({
query: (credentials) => ({
url: '/user/login',
method: 'POST',
body: credentials,
}),
onQueryStarted: async (credentials, { dispatch, queryFulfilled }) => {
try {
const { data } = await queryFulfilled;
dispatch(setAuthToken(data.body.token));
} catch(error) {
dispatch(setAuthToken(null));
}
},
}),
getProfile: builder.mutation({
query: () => ({
url: '/user/profile',
method: 'POST',
}),
}),
updateProfile: builder.query({
query: () => ({
url: '/user/profile',
method: 'PUT',
}),
}),
}),
})
export const {
useAuthMutation,
useGetProfileMutation,
useUpdateProfileQuery
} = bankApi;
评论