createAsyncThunk unable to dispatch rejected action in MERN application - reactjs

I am working on one MERN application and using Redux toolkit to handle asynchronus task on react side.
I noticed that whenever I tried to sign-in in app with invalid credential, createAsyncThunk is unable to dispatch rejected action.
However, in network request I am getting error response with status code 404, but I am getting payload undefined in rejected lifecycle.
Is there any solution to fix that problem with createAsyncThunk ?
controller(sign-in) :
export const signin = async (req, res) => {
const { email, password } = req.body;
try {
const oldUser = await UserModal.findOne({ email });
if (!oldUser)
return res.status(404).json({ message: "User doesn't exist" });
const isPasswordCorrect = await bcrypt.compare(password, oldUser.password);
if (!isPasswordCorrect)
return res.status(400).json({ message: "Invalid credentials" });
const token = jwt.sign({ email: oldUser.email, id: oldUser._id }, secret, {
expiresIn: "1h",
});
res.status(200).json({ result: oldUser, token });
} catch (err) {
res.status(500).json({ message: "Something went wrong" });
}
};
authSlice.js :
import { createAsyncThunk, createSlice } from "#reduxjs/toolkit";
import axios from "axios";
import * as api from "./api";
export const login = createAsyncThunk(
"auth/login",
async (formValue) => {
const response = await api.signIn(formValue);
return response.data;
}
);
const authSlice = createSlice({
name: "auth",
initialState: {
user: {},
error: "",
loading: false,
},
extraReducers: {
[login.pending]: (state, action) => {
state.loading = true;
},
[login.fulfilled]: (state, action) => {
state.loading = false;
state.user = action.payload.result;
},
[login.rejected]: (state, action) => {
state.loading = false;
state.error = action.payload.message;
},
},});
export default authSlice.reducer;
Unable to get payload in case of failure
But getting response in network tab

Related

401 Unauthorized react axios

I am trying to make an ecommerce using react, redux toolkit and axios
the problem is that I want the user to log in and get his cart from the backend right after the login
it always fails the and says (unauthorized) when i first login because it can't find the token
then after refresh it says unauthorized one more time
after the third refresh it works
this is my get cart
export const getCart = createAsyncThunk("cart/getcart", async () => {
const response = await axios.get("http://127.0.0.1:8000/techcart/get_cart/", {
headers: {
Authorization: `Token ${token}`,
},
});
return response.data;
});
const cartSlice = createSlice({
name: "cart",
initialState: {
cart: [],
cartItemsIds :[],
},
builder.addCase(getCart.fulfilled, (state, action) => {
state.cart = action.payload;
and this is my login function
export const login = createAsyncThunk(
"auth/login",
async ({ email, password }, thunkAPI) => {
try {
const response = await axios.post(
"http://127.0.0.1:8000/techcart/login/",
{ username: email, password }
);
localStorage.setItem("user", JSON.stringify(response.data));
return response.data;
} catch (error) {}
}
);
const initialState = user
? { isLoggedIn: true, user }
: { isLoggedIn: false, user: null };
builder.addCase(login.fulfilled, (state, action) => {
state.isLoggedIn = true;
state.user = action.payload;
here is where i am doing the login
const HandleLogin = () => {
dispatch(login({ email, password }));
};
useEffect(()=> {
if(isLoggedIn){
navigate('/')
dispatch(getCart())
}
},[isLoggedIn])
Cart page
useEffect(() => {
dispatch(getCart());
}, []);
here is where im defining my token :
export let user = JSON.parse(localStorage.getItem("user")) ? JSON.parse(localStorage.getItem("user")) : null;
export let userId = user ? user.user_id : null;
export let token = user!=null ? user.token : null;
and here is where im importing it in my cart slice
import { user, token } from "../../constants";
im using redux persist to persist the state of my cart
if anyone can help me i'm so thankful
here is what happens
You're initializing your token directly when your js is executed. So when you retrieve it, it is undefined.
Ans when you do the login, you're indeed storing your token, but you're not updating it in your application.
I can see you're using redux, so store your token in your redux store, and before sending your api call to retrieve your cart, retrieve your token from redux, to always have the latest value of your token

How to get a custom error message from API, using the Redux toolkit in React Js

In my backend application, I am sending an error of status code 404 with the message:
return res
.status(404)
.send({ message: "You need to complete the previous step" });
And in the front end, I am Using the Redux toolkit for handling the API Request with Axios.
My main goal is, every time if I get an error, I will set the error message as the message I sent from the backend.
export const createCourse = createAsyncThunk(
"test",
async (id) => {
return axios.post(`backendurl`,{});
}
);
But the problem I faced was that when the reducer gets rejected, it doesn't give me the message that I was sent from the backend.
.addCase(createCourse.rejected, (state, action) => {
console.log(action);
state.isLoading = false;
})
Here is the console of this problem:
{
"type": "/assignedjobs/create/:id/rejected",
"meta": {
"arg": "63bbd17d322112937f248099",
"requestId": "-6wZtw96-43ykgyeRRh7I",
"rejectedWithValue": false,
"requestStatus": "rejected",
"aborted": false,
"condition": false
},
"error": {
"name": "AxiosError",
"message": "Request failed with status code 404",
"stack": "AxiosError: Request failed with status code 404\n at settle (http://localhost:3000/static/js/bundle.js:201425:12)\n at XMLHttpRequest.onloadend (http://localhost:3000/static/js/bundle.js:200133:66)",
"code": "ERR_BAD_REQUEST"
}
}
How can I get the error message in my action payload if it gets rejected?
I am trying to achieve this by trycatch block in Axios but it doesnt make any change.
You can access the error message in the error object :
.addCase(createCourse.rejected, (state, action) => {
console.log(action.error.message);
state.isLoading = false;
state.error = action.error.message;
})
Actually you need to do like that.
export const createCourse = createAsyncThunk(
'create-course', async (data, { rejectWithValue }) => {
try {
const result = await axios.post(`backendurl`, data)
return result.data
} catch (error) {
if (error.response.status === 404) {
return rejectWithValue({ message: error.response.data.message })
}
return rejectWithValue({message: 'Some Other message'})
}
}
)
inside reducer
.addCase(createCourse.rejected, (state, action) => {
state.error = action.payload.message
state.isLoading = false;
})
Result of error.response

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

Redux toolkit action is stuck in pending state even after api response is recieved

I am using redux toolkit and all of the other actions for slice are working as expected but only editCountry action has problem. My editCountry action is stuck in country/editCountry/pending. I have no idea what is wrong with code.
This is how redux slice code is defined.
slice.js
const initialState = {
countries: {},
isLoading: false,
isError: false,
isSuccess: false,
message: ''
}
export const editCountry = createAsyncThunk(
'country/editCountry',
async (payload, thunkAPI) => {
try {
const response = await apiProvider.editCountry(payload);
return response;
}
catch (e) {
return thunkAPI.rejectWithValue(e)
}
})
const countriesSlice = createSlice({
name: 'country',
initialState,
reducers: { },
extraReducers: (builder) => {
builder
.addCase(editCountry.pending, (state, action) => {
state.isLoading = true
})
.addCase(editCountry.fulfilled, (state, action) => {
console.log('fulfilled before: ', state); // shown on console
console.log('fulfilled action: ', action.payload.data); // undefined
const unUpdatedCountries = state.countries.data.filter(c => c._id !==
action.payload.data._id)
const updatedCountries = [action.payload.data, ...unUpdatedCountries];
state.isLoading = false;
state.isSuccess = true;
state.countries = {...state.countries, data: updatedCountries };
console.log('fulfilled after: ', state); // do not shown on console
})
.addCase(editCountry.rejected, (state, action) => {
state.isLoading = false;
state.isError = true;
state.message = action.payload;
})
}
Here is the Api helper method which is invoked inside editCountry action.
ApiProvider.js
export const editCountry = async (payload) => {
let token = getToken();
const config = {
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
Accept: "application/json",
},
};
try{
const {data} = await axios.put(`${baseUrl}/edit-country`, payload, config);
if(data.status){
return Promise.resolve(data);
}
return Promise.reject(data.message);
}
catch (e) {
return Promise.reject(e.message);
}
}
And here is my Submit method from where action is dispatched.
Country.js
const handleSubmit = async (e, newFormData) => {
e.preventDefault()
const apiPayload = {
Country_ID: newFormData.id,
Name: newFormData.name,
DialCode: newFormData.dialCode.includes("+") ? newFormData.dialCode : "+"+newFormData.dialCode,
ISO: newFormData.iso,
CallRate: newFormData.minutePrice,
Blocked: newFormData.isBlocked.value,
}
try {
await dispatch(editCountry(apiPayload)).unwrap()
toggle() // hide form modal
toast.success('Country updated successfully');
}
catch (e) {
setFormData({
...formData,
errorMsg: e.message,
});
toggle();
toast.error(e);
}
}
On action dispatch redux is stuck on pending but in network tab api call is completed.
[enter image description here][1]
Network tab image:
[1]: [https://i.stack.imgur.com/X3rbA.png][1]
Redux dev tools image:
[1]: https://i.stack.imgur.com/sQUdJ.png
Just return the data, don't return wrap it in Promise.resolve.
if(data.status){
return Promise.resolve(data);
}
return Promise.reject(data.message);
}
In editCountry.fulfilled, it should be action.payload not action.payload.data.
In editCountry, you send data and in editCountry async thunk, you send the complete response which is basically the data. In editCountry.fulfilled, you have complete data in payload but you are trying to access the data from payload.
Hope this solve your issue

Socket.io - Terminal returning data as "null"

I'm building a forum app using reactjs, nodejs, express and mongoDB, where users can create account, login and then post a post text. I want this post to be displayed instantly To all others users, for that i am using socket.io server.
I would appreciate if you check my whole project here in github: (https://github.com/darkpsinight/forum)
However, whenever a user login or connect to the app, i get this null in socket terminal:
Connected user.✅
null
I tried to investigate this null message, and i discovered that data is returning null.
How to make data returns data of createdPostsocket instead of null ?
Edit:
createdPostsocket is initialized as null, and my catch statement returns null, so either i am not assigning a value to createdPostsocket or i am throwing an error in my promise.
Meanwhile, i already assigned a value in Slice at fulfilled case :
[createPost.fulfilled]: (state, action) => {
console.log(action.payload);
window.location.reload(false);
state.createdPostsocket= action.payload.data.data
}
Which means createdPostsocket assigned the value of payload.data.data, right?
My codes:
- Back:
app.js (socket server)
const io = require("socket.io")(4000, {
cors: {
origin: "http://localhost:3000",
},
});
io.on('connection', (socket) => {
console.log('Connected user.✅');
socket.on('addPost', data => {
console.log(data); //returning null in terminal
io.emit('newPost', data)
})
socket.on('disconnect', function (socket) {
console.log('Disconnected user.⛔️');
});
});
- Front:
postsSlice.js:
const initialState = {
posts: [],
createdPostsocket: null
};
const postsSlice = createSlice({
name: "posts",
initialState,
reducers: {},
extraReducers: {
//Create http request 3 cases
[createPost.pending]: (state, action) => {
},
[createPost.fulfilled]: (state, action) => {
console.log(action.payload);
window.location.reload(false);
state.createdPostsocket= action.payload.data.data
},
[createPost.rejected]: (state, action) => {
},
},
});
export const { } = postsSlice.actions;
export const selectPosts = (state) => state.posts
export default postsSlice.reducer;
index.jsx:
const posts = useSelector(selectPosts)
const socket = useRef()
useEffect(() => {
socket.current = io("ws://localhost:4000")
}, [])
useEffect(() => {
socket.current.emit("addPost", posts.createdPostsocket);
console.log (posts.createdPostsocket) //returning null in console developper
}, [posts.createdPostsocket])
postsAPI.js
import { axiosInstance } from "../../config/axios";
import { requests } from "../../config/requests";
//post service
export const PostsService = {
// Create post request
create: (data) => {
console.log(data);
return axiosInstance
.post(requests.postapi.create, data, { credentials: "include" })
.then((res) => {
return res;
})
.catch((err) => {
return err;
});
},
};
- back:
controller.js:
module.exports = {
create: (req, res) => {
const data = {
user: req.user.sub,
text: req.body.text,
/* image: req.file && req.file.filename */
}
console.log(data) //returning user and text
const t = new post(data)
t.save().then(t => t
.populate('user')
.then(post => {
res.status(200).json({
message: 'post created',
data: post
})
})
.catch(err => {
res.status(500).json({
message: 'post not created',
data: null
})
})
)
},
}
Remove window.location.reload(false); so you wont lose your data. window.location.reload(false); will reload your page and delete all the data. However it is not recommanded to use vanilla JavaScript to call the reload method to tell the browser to reload the current page and you will loose any data.

Resources