我无法发送我的授权令牌来使用 RTK Query 检索配置文件

I don't manage to send my authorization token to retrieve a profile with RTK Query

提问人:Bidou 提问时间:8/6/2023 最后编辑:Drew ReeseBidou 更新时间:8/7/2023 访问量:492

问:

我已经做了 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>
  );
}

它看起来像什么:

我尝试过:

  • 阅读有关 RTK Query 的文档
  • 观看 RTK Query 教程
  • 尝试检索令牌并通过以下方式进行设置prepareHeaders
  • 尝试在查询中检索令牌prepareHeadersgetProfile
  • 已尝试将令牌传递给useGetProfileMutationLoginpage.jsx
  • 已尝试将令牌存储在其中并检索它以将其传递给localStorageuseGetProfileMutation
  • 尝试等待登录成功后再调用useGetProfileMutation
JavaScript ReactJS 身份验证 redux-toolkit

评论

0赞 jifakir 8/6/2023
您是否坚持了身份验证响应?

答:

1赞 jifakir 8/6/2023 #1

代码中的主要问题是您没有坚持身份验证响应。这就是为什么你的 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>
    );
}
1赞 Drew Reese 8/7/2023 #2

通常,您可能需要在 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;

评论

0赞 Bidou 8/7/2023
谢谢!我不知道onQueryStarted,我把调度放在我的Loginpage.jsx中。这是一个不好的做法吗?与乐观更新有关的东西?因为我这样做的方式是,我的令牌被调度了两次,因为有两次重新渲染。
0赞 Drew Reese 8/7/2023
@Bidou 不好的做法,不。它基本上保存了一个步骤(重新渲染组件,使其具有更新的查询/突变结果),因为您还可以调度一个操作来更新存储。