Redux toolkit Bearer token undefined - reactjs

Error: authorization bearer undefined is shown in the browser on the .
Here is my
Here is my code of useEffect of OrderScreen.js Here I have tried to dispatch check if user have value or not if not redirect them to /login which is login page. I am redirecting to login page because from use state i am not getting any value.
const dispatch = useDispatch()
const navigate = useNavigate()
const { user } = useSelector((state) => state.auth)
const { orders, isLoading, isError, message } = useSelector(
(state) => state.orders,
)
useEffect(() => {
if (isError) {
toast.error(message)
}
if (!user && !user.isAdmin) {
navigate('/login')
}
dispatch(getOrder())
return () => {
dispatch(reset())
}
}, [user, isError, message, dispatch, navigate])
`
Here is my orderSlice. for the getOrder Function `
const initialState = {
orders: [],
isError: false,
isSuccess: false,
isLoading: false,
message: '',
}
export const getOrder = createAsyncThunk(
'orders/getOrder',
async (_, thunkAPI) => {
try {
const token = thunkAPI.getState().auth.user.token
return await orderService.getOrder(token)
} catch (error) {
const message =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString()
return thunkAPI.rejectWithValue(message)
}
},
)
`
Here is my orderService.js `
const getOrder = async (token) => {
const config = {
headers: {
Authorization: `Bearer ${token}`,
},
}
const response = await axios.get(API_URL, config)
return response.data
}
`
I tried to solve from these stacks
Authorization Bearer Token Header in Javascript
How to send bearer token through header of axios call in react redux

You can try to pass the token as first argument of your thunk function ( currently "_") to make sure it is not undefined. Also, you can use a debugger to know the actual state of the global store (or simply log it in your component).
And lastly, try to log what returns the thunkApi.getState() function.

Related

how to using refresh token in react

I want to use my refresh token to get a new access token in a react application. I send a request to an API to get data about books. if I get 401 or 500 error I want to send a request to another API with my refresh token to get a new access token to send to first API to get data about books.
here I use 2 useEffect first for getting books data and second to get new access token . also I have a setInterval to run first useEffect which gets books data. If I change access token in localstorage and crash it deliberately to get 401 error manually I want that new access token that comes from refresh token makes access token in localstorage correct again so stop page from crashing.so my problem is 2 things: first I dont know what to do with my new accesstoken . second is When I change accesstoken in localStorage to manually get 401 error , if I refresh the page I want to my localStorage set my new access token so page does not crash.
here is my useContext and my component which handles these two useEffects:
here is my useContext hook:
import React from "react";
import { useState } from "react";
const AuthContext = React.createContext({
token: "",
refreshToken: "",
isLoggedIn: false,
login: () => {},
logout: () => {},
booksData: [],
});
export const AuthContextProvider = (props) => {
let initialToken = localStorage.getItem("token");
let initialRefreshToken = localStorage.getItem("refresh-token");
const [token, setToken] = useState(initialToken);
const [refreshToken, setRefreshToken] = useState(initialRefreshToken);
const isUserLoggedIn = !!token;
const logoutHandler = () => {
setToken(null);
localStorage.removeItem("token");
localStorage.removeItem("books");
localStorage.removeItem("refresh-token")};
const loginHandler = (token, refreshToken) => {
setToken(token);
setRefreshToken(refreshToken);
localStorage.setItem("token", token);
localStorage.setItem("refresh-token", refreshToken);
};
const contextValue = {
token,
isLoggedIn: isUserLoggedIn,
refreshToken,
login: loginHandler,
logout: logoutHandler,
};
return (
<AuthContext.Provider value={contextValue}>
{props.children}
</AuthContext.Provider>
);
};
export default AuthContext;
and here is my component:
const Books = () => {
const ctx = useContext(AuthContext);
const [books, setBooks] = useState([]);
const [reqCounter, setReqCounter] = useState(0);
const [tokenError, setTokenError] = useState(false);
useEffect(() => {
const fetchData = async () => {
let response = await fetch("some API endpoint", {
method: "GET",
headers: {
Authorization: `Bearer ${ctx.token}`,
},
});
try {
const data = await response.json();
if (response.status === 200) {
setBooks(data.books);
} else if (response.status === 404) {
setError("No page found");
} else if (response.status === 403) {
setError("You dont have accsess to this page");
}
} catch (error) {
setTokenError(true);
}
};
fetchData();
}, [ctx.token, reqCounter, ctx]); // Is my dependencies right??
setInterval(() => {
setReqCounter(reqCounter + 1);
}, 5000);
useEffect(() => {
const refresh = async () => {
const response = await fetch("some API", {
method: "POST",
body: JSON.stringify({
refresh_token: ctx.refreshToken,
}),
headers: {
"Content-Type": "application/json",
},
});
const data = await response.json();
if (response.ok) {
// Dont Know what should I write here!
}
};
refresh();
}, [tokenError]); // Is my dependencies right??
const content = books.map((item) => (
<BookItem
title={item.name}
year={item.publish_date}
pages={item.pages}
author={item.Author}
img={item.thumbnail}
key={item.name}
/>
));
return (
<section className={classes.bookPage}>
{!error && books.length !== 0 && (
<ul className={`list ${classes.booksList}`}>{content}</ul>
)}
{error && <h2 className={classes.error}>{error}</h2>}
{isLoading && <PulseLoader color="#f53e3e" className={classes.spinner} />}
</section>
);
};
export default Books;
Suggestions
Ideally Handle fetch with token and token refresh in one place, something like HttpContext
but to check you can start with existing authcontext
you can refresh token on regular intervals
or when the call in unauthorized
issues:
when token expires, some call will fail, which needs to be made again with a new token
When token is refreshed at regular interval, if the old token is invalidated, some call in the queue with older token could fail
pseudo code
in AuthContext
const fetchData = async (link) => {
try {
let response = await fetch(link, {
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
},
});
} catch(error) {
// check status and attempt refresh
// but existing calls will old token will fail,
// can will cause multiple refresh token to be called
}
}
//or refresh token on regular interval
useEffect(() => {
const timerId = setInterval(() => {
// refresh token and set token
// The problems is the moment the token is refreshed, the old token might get invalidated and some calls might fail
}, tokenRefershTimeInMilliSec)
return () => {
clearInterval(timerId)
}
}, [])
...
const contextValue = {
token,
isLoggedIn: isUserLoggedIn,
refreshToken,
login: loginHandler,
logout: logoutHandler,
get: fetchData
};
return <AuthContext.Provider value={contextValue} {...props}> // pass all props down
or use a http context to seperate concerns
const initalValue = // some value
const HttpContext = React.createContext(initalValue);
const initialToken = // from localstorage
const HttpContextProvider = (props) => {
const [token, setToken] = useState(initialToken)
const fetchData = async (link) => {
try {
let response = await fetch(link, {
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
},
});
} catch(error) {
// check status and attempt refresh
// but existing calls will old token will fail,
// can will cause multiple refresh token to be called
}
}
const value = useMemo(() => {
return {
get: fetchData,
// post, put, delete
}}, [token]
//refresh token on regular interval
useEffect(() => {
const timerId = setInterval(() => {
// refresh token and set token
// The problems is the moment the token is refreshed, the old token might get invalidated and some calls might fail
}, tokenRefershTimeInMilliSec)
return () => {
clearInterval(timerId)
}
}, [])
return (<HttpContext.Provider {...props}>)
}
if you can are using axios, then you can check way to auto refresh or use libraries like axios-auth-refresh
Hope it points you in the right direction

How I should get token and send it in API request in redux/toolkit

I just migrate to redux/toolkit and I'm a little confused about obtain the token of the user because I'm not sure if I'm doing right in only calling the local storage instead of getState() as I usually do before with the previous redux.
This is the code:
// VideoSlice
const user: User = localStorage.getItem('user') ? JSON.parse(localStorage.getItem('user')!) : null;
export const uploadVideo = createAsyncThunk("videos/uploadStatus", async ( input: VideoInput , thunkApi) => {
try {
return await videoService.uploadVideo(input, user.token);
} catch (error: any) {
const message = (error.response && error.response.data && error.response.data.message) ||
error.message ||
error.toString();
return thunkApi.rejectWithValue(message);
}
});
// VideoService
// AXIOS
import Axios from 'axios';
const API_URL = '/api/video';
const uploadVideo = async ( input: VideoInput, token: string ) => {
const { data } = await Axios.post(`${API_URL}`, input, {
headers: {
'x-token': token
}
});
return data;
}
const videoService = {
getVideos,
uploadVideo,
}
export default videoService;
If you are keeping the token in localstorage then you can leverage that for API call, you can also get the state using thunkAPI.
const {user}=thunkAPI.getState().auth //assuming you have the auth slice with an user and its token properties
const {token}=user
But again, on page refresh the redux state is lost so local storage has to be used.

State updates but not in component

I am using redux-toolkit and I was trying to handle request errors. after trying console loging I found out that my error state updates but not when I need.
const ProductCreatePage = () => {
const {token} = useContext(UserContext);
const {error} = useSelector(state => state.ui)
const dispatch = useDispatch()
const navigate = useNavigate();
const createProductHandler = async (event) => {
event.preventDefault();
const form = document.querySelector('form');
const productData = {
price: Number.parseInt(event.target.price.value),
name: event.target.name.value,
status: event.target.status.value === "true" ? true : false
};
const formData = new FormData();
event.target.querySelectorAll("input").forEach(({ name, type, value, files, ...element }) => {
if (type === 'file') {
formData.append(`files.img`, files[0], files[0].name);
}
});
formData.append('data', JSON.stringify(productData));
await dispatch(createProduct({
productData: formData,
token
}))
console.log(error)
if(error === false){
// navigate(routes.products,{state:{create: true}})
console.log("sss")
}
}
return(...)
}
this is the function that creates product in redux using redux-toolkit
export const createProduct = ({productData,token}) => {
return async (dispatch) => {
try {
dispatch(ProductSliceAction.loadingToggle())
const {data} = await axios.post(`https://www.youtube.com/watch?v=xWpnTGmS8-Q`,productData,{
headers: {
Authorization: `Bearer ${token}`
}
})
dispatch(UiSliceAction.resetErrors())
dispatch(ProductSliceAction.loadingToggle())
}catch (error){
dispatch(UiSliceAction.setErrors({
message: error.message,
code: error.response.status
}))
dispatch(ProductSliceAction.loadingToggle())
}
}
}
and this is my error redux slice
const initialState = {
error: false
}
const UiSlice = createSlice({
name: "ui",
initialState,
reducers: {
setErrors: (state,{payload}) => {
state.error = payload;
},
resetErrors: (state) => {
state.error = initialState.error;
}
}
});
I want to handle errors like "Network Error" , 403 , ... and store the error in UiSlice error and for doing that I am using dispatch like below
dispatch(UiSliceAction.setErrors({
message: error.message,
code: error.response.status
}))
the state updates but this update not effecting code below
if(error === false){
// navigate(routes.products,{state:{create: true}})
console.log("sss")
}
I did multiple console.log and I found out state does not update in component (it updates in reducer when use console.log)
now I want to know where is the problem. why my state updates with delay(after exciting if(error === false))
this is the logs
enter image description here

How can I use SWR hook for all API methods? (CRUD)

I am switching certain CRUD functionality that I used to provide with a token, but now I am using SWR and I don't know how to convert it.
I used this hook for GET methods but for others, I don't know what to do!
export default function useGetData(apiKey) {
const fetcher = async (...args) => await fetch(...args).then(res => res.json());
const { data, mutate, error } = useSWR(apiKey, fetcher);
const loading = !data && !error;
return {
loading,
user: data,
mutate
}
}
OK, I found the answer :
import useSWR from 'swr';
import { getTokenFromLocalStorage } from '../../services/storage';
export default function useGetData({ url, payload, options = {}}) {
const mainUrl = process.env.NEXT_PUBLIC_BASE_URL + URL;
const method = payload ? 'POST' : 'GET';
const fetcher = async () => {
const headers = {
Authorization: `Bearer ${getTokenFromLocalStorage()}`
};
const options = {
method,
headers,
...(payload && { body: payload }),
};
return await fetch(mainUrl, options).then((res) => res.json());
};
const defaultOptions = {
revalidateIfStale: false,
revalidateOnFocus: false,
revalidateOnReconnect: false,
};
const { data, mutate, error, isValidating } = useSWR(url + method, fetcher, {
...defaultOptions,
...options,
});
const loading = !data && !error;
return { data, loading, error, mutate, isValidating };
}

React Log out all Pages using Context and Axios interceptors

I am outside a React component but need to update my auth context 'AuthContext' to delete the existing user to log the user out entirely from all logged in pages
I'm able to dispatch a redux action using Redux. How can I achieve the same using Context?
Below are my code... the logOutAll function is not working because useContext can't be used outside of a React component or function
Appreciate advise from those who have successfully implemented this. Thanks
import { useContext } from "react"
import axios from "axios"
import { AuthContext } from "../context/authContext"
const api = axios.create({
baseURL: "http://localhost:5000/api",
headers: {
"Content-Type": "application/json",
},
})
/**
intercept any error responses from the api
and check if the token is no longer valid.
ie. Token has expired or user is no longer
authenticated.
logout the user if the token has expired
**/
// const logOutAll = () => {
// const { authStatus, setAuthStatus } = useContext(AuthContext)
// console.log(authStatus)
// setAuthStatus({
// ...authStatus,
// isAuthenticated: false,
// isLoading: false,
// user: null,
// })
// }
api.interceptors.response.use(
res => res,
err => {
if (err.response.status === 401) {
console.log("log out all")
logOutAll() //! CANNOT WORK BECAUSE UseContext cannot be called outside React function
or component
}
return Promise.reject(err)
}
)
export default api
I set up my axios interceptors in my app.js file instead and it seems to be working fine
const App = () => {
const { authStatus, setAuthStatus } = useContext(AuthContext)
useEffect(() => {
const checkLoggedIn = async () => {
let token = localStorage.getItem("#token")
if (token === null) {
localStorage.setItem("#token", "")
token = ""
}
const res = await api.get("/users/me", {
headers: {
authorization: token,
},
})
//TODO
// const token = localStorage.getItem("#token")
// console.log("TOKEN", token)
// if (token) {
// setAuthToken(token)
// }
// const res = await api.get("http://localhost:5000/api/users/me")
console.log(res)
setAuthStatus(authStatus => ({
...authStatus,
isAuthenticated: true,
isLoading: false,
user: res.data,
}))
}
checkLoggedIn()
}, [setAuthStatus])
api.interceptors.response.use(
res => res,
err => {
if (err.response.status === 401) {
setAuthStatus({
...authStatus,
isAuthenticated: false,
isLoading: false,
user: null,
})
}
return Promise.reject(err)
}
)

Resources