I’ve been at it for 3 days now and I need some help.
I’m working on a project from a course I follow where the use of React + Redux Toolkit (RTK) is mandatory.
I found RTK Query and I managed to send via POST, a login + password to my API and get the proper response from my Express server (back-end was already done and given to me): Login incorrect, password incorrect, login successful.
I can properly console log my token afterwards.
But then, I need to do another POST to retrieve a profile (firstname, lastname, username) and for this, I need to put the received token in my POST headers. And this is where I’m stuck.
I have no idea how to debug all this. I spent the last two days watching/reading tutorials, documentation, I even asked ChatGPT, to no avail. I can’t fill out my POST headers with the token and since my token is always undefined, the issue must be here:
const token = getState().auth.data.body.token;
I can’t figure out what the path should be to retrieve the token.
I’m pretty sure the answer is easy and that I’m missing something obvious and in front of my eyes, but I don’t find the issue.
Here is my API (localhost:3001/api/v1):
POST user/login
response body: (this one works)
{
"status": 200,
"message": "User successfully logged in",
"body": {
"token": ""
}
}
POST user/profile
response body: (this one I can’t retrieve)
{
"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"
}
}
I don’t post the user update as it is not relevant here.
Here are my files:
store.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
Loginpage.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>
);
}
What it looks like:
- email error: https://i.snipboard.io/PDRzNv.jpg
- password error: https://i.snipboard.io/vKZI2t.jpg
- login successful: https://i.snipboard.io/Jz84H1.jpg
- token missing from header after clicking get profile: https://i.snipboard.io/RedpYj.jpg
What I tried:
- Read the documentation about RTK Query
- Watch RTK Query tutorials
- Tried to retrieve the token and set it via
prepareHeaders
- Tried to retrieve the token with
prepareHeaders
in thegetProfile
query - Tried to pass the token to
useGetProfileMutation
inLoginpage.jsx
- Tried to store the token in
localStorage
and retrieve it to pass it touseGetProfileMutation
- Tried to wait for the login to be successful to then call
useGetProfileMutation
2
Answers
The main issue in your code is you are not persisting in your auth response. That’s why your getState() method is not able to consume token. So, you have to persist your token using localStorage or any persisting library like redux-persist. Here I am gonna show modified code using redux-persist.
Create a slice named "authSlice.js" like this to store auth response:
then install redux-persist and under
store.js:
apiSlice.js:
Now modify login page as below:
Login.jsx
Oftentimes you may need to persist some cached queries/mutations outside the API slice. Create an auth slice to hold the auth object reference. You’ll then be able to dispatch an action from the query/mutation to update the auth state using onQueryStarted, which can then be accessed in the base query function for the purpose of setting auth headers with stored token values.
Example:
auth.slice.js
store.js
api.slice.js