redux-toolkit delete all items when i try to delete one - reactjs

i'm migrating slowly to redux-toolkit
when i try to delete a single item from my store, the action works well because i send the right ID from the component, but i think that the way i send this id to the slice is incorrect so i'm not able to delete the right item from my store(it delete all the items)
component dispatch code:
const handleDelete = async (e) => {
e.preventDefault()
const del_id = e.currentTarget.getAttribute("data-id")
dispatch(deleteComment(del_id))
}
action & api call:
export const deleteComment = (id) => async (dispatch, getState) => {
try {
dispatch(deleteCommentsLoading())
const {
userLogin: { userInfo },
} = getState()
const config = {
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${userInfo.token}`,
},
}
const { data } = await axios.delete(
`${process.env.REACT_APP_API_KEY}/publication/comment/delete/${id}`,
config
)
dispatch(deleteCommentsSuccess({ commentId: id }))
// i tried deleteCommentsSuccess(data); (id)... nothing work to send this id to the slice
} catch (error) {
dispatch(
deleteCommentsFail(
error.response && error.response.data.message
? error.response.data.message
: error.message
)
)
}
}
my slice:
const commentSlice = createSlice({
name: "comments",
initialState: {
comment: {},
loading: false,
error: false,
},
... other reducers
// DELETE COMMENT
deleteCommentsLoading: (state) => {
state.loading = true
},
deleteCommentsFail: (state, action) => {
state.error = action.payload
state.loading = false
},
deleteCommentsSuccess: (state, action) => {
const { commentId } = action.payload
state.comment.comments.filter((item) => item._id !== commentId)
// i tried first when i send data or id to put action.payload.id nothing work
state.loading = false
state.error = false
},
and this's my comments store slice, every item have his own "_id"

How is your backend. (comment)
Something like this? :
const receita = await Recipe.findById(req.params.id)
if (receita) {
await receita.remove()
res.json({ message: 'Receita removed' })
} else {
res.status(404)
throw new Error('Receita not found')
}
})

state.comment.comments.filter((item) => item._id !== commentId) will just return a filtered copy of the array,but not actually modify anything.
You have to do
state.comment.comments = state.comment.comments.filter((item) => item._id !== commentId)

Related

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.

Redux filter array, UI doesn't update

Using redux filter to remove an item yet it's removing from the DB but the UI isn't updating, not sure if i'm missing something stupid or being dumb.
The expected functionality would be that it removes the item from the database and UI would update
If i manually refresh the page, it's as it should be.
This is inside my goalService
const deleteGoal = async (goalId, token) => {
const config = {
headers: {
Authorization: `Bearer ${token}`,
},
};
const response = await axios.delete(API_URL + goalId, config);
return response.data;
};
inside goalSlice
export const deleteGoal = createAsyncThunk(
"goals/delete",
async (id, thunkAPI) => {
try {
const token = thunkAPI.getState().auth.user.token;
return await goalService.deleteGoal(id, token);
} catch (error) {
const message =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
return thunkAPI.rejectWithValue(message);
}
}
);
export const goalSlice = createSlice({
name: "goal",
initialState,
reducers: {
reset: (state) => initialState,
},
extraReducers: (builder) => {
builder
.addCase(deleteGoal.pending, (state) => {
state.isLoading = true;
})
.addCase(deleteGoal.fulfilled, (state, action) => {
state.isLoading = false;
state.isSuccess = true;
state.goals = state.goals.filter(
(goal) => goal._id !== action.payload.id
);
})
.addCase(deleteGoal.rejected, (state, action) => {
state.isLoading = false;
state.isError = true;
state.message = action.payload;
});
},
});
Edit 1:
goals: {
goals: [
{
user: '624dfed264387649da83d8db',
text: 'dylanjcain',
_id: '624f53d6fd65e29ed17506e3',
createdAt: '2022-04-07T21:12:54.748Z',
updatedAt: '2022-04-07T21:12:54.748Z',
__v: 0
}
],
isError: false,
isSuccess: true,
isLoading: false,
message: ''
}
Response from API
{
"_id": "624f554afd65e29ed17506e6",
"user": "624dfed264387649da83d8db",
"text": "test123",
"createdAt": "2022-04-07T21:19:06.435Z",
"updatedAt": "2022-04-07T21:19:06.435Z",
"__v": 0
}
Remember that the action payload for the thunk actions is the return value of the thunk. So in the delete case its return await goalService.deleteGoal(id, token); which ultimately resolves to return response.data; which is the response from your API.
So unless the API is returning a shape like { id: 123 } when you make your delete request your filter won't filter anything. Check to see that the API is giving the ID back. Otherwise you'll want to return {id: goalId} rather than response.data from your deleteGoal async function.
Solved:
(goal) => goal._id !== action.payload.id
So turns out i was being dumb, here's what i changed.
(goal) => goal._id !== action.payload._id

Update Values of Multiple Array in Redux

I'm updating an array and I wanted to update the productCode based on the given newCode response. This is by clicking the 'CREATE ALL PRODUCTS' button.
I'm thinking that the problem is on the reducer. It's currently not updating the productCode and newProductCode
Tip: productIndex is the key to finding it
Click Here: CODESANDBOX
Action
export const createAllProducts = (products) => async (dispatch) => {
try {
dispatch({
type: appConstants.CREATE_ALL_PRODUCTS_REQUEST
});
const responses = [
{
config: null,
data: {
newCode: "NEW_AA"
},
headers: null
},
{
config: null,
data: {
newCode: "NEW_FF"
},
headers: null
},
{
config: null,
data: {
newCode: "NEW_GG"
},
headers: null
}
];
const finalResponses = responses.map((product, index) => ({
newProductCode: product.data.newCode,
productCode: product.data.newCode,
productIndex: products[index].productIndex
}));
console.log(finalResponses);
dispatch({
type: appConstants.CREATE_ALL_PRODUCTS_SUCCESS,
payload: finalResponses
});
} catch (error) {
dispatch({
type: appConstants.CREATE_ALL_PRODUCTS_FAILURE
});
}
};
Reducer
case appConstants.CREATE_ALL_PRODUCTS_SUCCESS:
const updatedProducts = state.products.map((product, index) => {
const found = action.payload.find((el) => el.productIndex === index);
return found
? {
...updatedProducts,
productCode: found.productCode,
newProductCode: found.newProductCode
}
: product;
});
return {
...state,
isCreatingAllProducts: false,
products: updatedProducts
};
The issue is with the reducer
case appConstants.CREATE_ALL_PRODUCTS_SUCCESS:
return {
...state,
products: state.products.map((product, index) => {
const found = action.payload.find((el) => el.productIndex === index);
console.log(found);
return found
? {
...product,
productCode: found.productCode,
newProductCode: found.newProductCode
}
: product;
})
};
You used reduce methods with the initial value state, which is the actually old state.
Consider this example:
const state = { history: null }
const payload = [ 'hello', 'equal' ]
//your current reducer
const newState = payload.reduce((acc, cur) => { acc[cur] = cur; return acc } , state)
//the state reference point to the same obj, then redux will not trigger re-render
console.log(newState === state) // true

Resources