React redux doesn't automatically render the changed things - reactjs

I have a problem with React redux. And I want to display the current state immediately. Unfortunately this doesn't work as intended. The changed data is only displayed correctly after the page has been reloaded.
This is the Main Part in my Articel.js
const buySomething = async (articelId) => {
await axios.put(`/articel/request/${articelId}`).then((res) => {
dispatch(requested(currentUser._id));
setSnackbarMessage(res.data);
setOpen(true);
});
};
Articel model:
requested: {
type: [String],
default: [],
},
articelSlice.js
const initialState = {
currentArticel: null,
loading: false,
error: false,
};
requested: (state, action) => {
if (!state.currentArticel.requested.includes(action.payload)) {
state.currentArticel.requested.push(action.payload);
} else {
state.currentArticel.requested.splice(
state.currentArticel.requested.findIndex(
(userId) => userId === action.payload
),
1
);
}
},
Complete articelSlice Code:
import { createSlice } from "#reduxjs/toolkit";
const initialState = {
currentArticel: null,
loading: false,
error: false,
};
export const articelSlice = createSlice({
name: "articel",
initialState,
reducers: {
fetchStart: (state) => {
state.loading = true;
},
fetchSuccess: (state, action) => {
state.loading = false;
state.currentArticel = action.payload;
},
fetchFailure: (state) => {
state.loading = false;
state.error = true;
},
requested: (state, action) => {
if (!state.currentArticel.requested.includes(action.payload)) {
state.currentArticel.requested.push(action.payload);
} else {
state.currentArticel.requested.splice(
state.currentArticel.requested.findIndex(
(userId) => userId === action.payload
),
1
);
}
},
like: (state, action) => {
if (!state.currentArticel.likes.includes(action.payload)) {
state.currentArticel.likes.push(action.payload);
state.currentArticel.dislikes.splice(
state.currentArticel.dislikes.findIndex(
(userId) => userId === action.payload
),
1
);
}
},
dislike: (state, action) => {
if (!state.currentArticel.dislikes.includes(action.payload)) {
state.currentArticel.dislikes.push(action.payload);
state.currentArticel.likes.splice(
state.currentArticel.likes.findIndex(
(userId) => userId === action.payload
),
1
);
}
},
},
});
export const {
fetchStart,
fetchSuccess,
fetchFailure,
like,
dislike,
requested,
} = articelSlice.actions;
export default articelSlice.reducer;

Related

Splitting the redux state where some variables are common

I'm using redux-toolkit for my chat application. Currently there are two slices in the store:
userSlice - for managing the user state
import { createSlice } from "#reduxjs/toolkit";
import appApi from "../services/appApi";
const endpoints = [
appApi.endpoints.signUpUser,
appApi.endpoints.loginUser,
appApi.endpoints.profileUser,
appApi.endpoints.logout,
];
export const userSlice = createSlice({
name: "user",
initialState: {
loading: false,
error: null,
_id: null,
},
reducers: {
addNotifications: (state, { payload }) => {},
resetNotifications: (state, { payload }) => {},
clearError: (state) => {
state.error = null;
state.loading = false;
},
setError: (state,{payload}) =>
{
state.error = payload
},
loadID: (state) => {
state._id =
JSON.parse(localStorage.getItem("cherry-chat-status")) || null;
},
setLoading: (state, { payload }) =>
{
state.loading = payload
}
},
extraReducers: (builder) => {
builder.addMatcher(
appApi.endpoints.signUpUser.matchFulfilled,
(state, { payload }) => {
localStorage.setItem("cherry-chat-status", JSON.stringify(payload._id));
return { ...state, loading: false, error: null, ...payload };
}
);
builder.addMatcher(
appApi.endpoints.loginUser.matchFulfilled,
(state, { payload }) => {
localStorage.setItem("cherry-chat-status", JSON.stringify(payload._id));
return { ...state, loading: false, error: null, ...payload };
}
);
builder.addMatcher(
appApi.endpoints.profileUser.matchFulfilled,
(state, { payload }) =>
{
return { ...state, loading: false, error: null, ...payload };
}
);
builder.addMatcher(
appApi.endpoints.profileUser.matchFulfilled,
(state, { payload }) => {
return { ...state, loading: false, error: null, ...payload };
}
);
builder.addMatcher(appApi.endpoints.logout.matchFulfilled, () => {
localStorage.removeItem("cherry-chat-status");
return { loading: false, error: null };
});
endpoints.forEach(({ matchPending, matchRejected }) => {
builder.addMatcher(matchPending, (state) => {
state.loading = true;
state.error = null;
});
builder.addMatcher(matchRejected, (state, { payload: error }) => {
state.error = error?.data?.message;
state.loading = false;
});
});
},
});
export const {
addNotifications,
resetNotifications,
clearError,
loadID,
setError,
setLoading,
} = userSlice.actions;
export default userSlice.reducer;
messageSlice - for managing the messages
import { createSlice } from "#reduxjs/toolkit";
import io from "socket.io-client";
import msgApi from "../services/msgApi";
const SOCKET_URL = "localhost:5000";
export const socket = io(SOCKET_URL);
const endpoints = [msgApi.endpoints.profileUserRooms];
export const messageSlice = createSlice({
name: "message",
initialState: {
rooms: [],
currentRoom: [],
members: [],
messages: [],
privateMemberMsg: {},
newMessages: {},
error: null,
},
reducers: {
addMembers: (state, { payload }) => {
state.members = payload;
},
},
extraReducers: (builder) => {
// your extra reducers go here
builder.addMatcher(
msgApi.endpoints.profileUserRooms.matchFulfilled,
(state, { payload }) => {
//I want set the loading state of the user Slice to be false
return {
...state,
rooms: payload,
};
}
);
endpoints.forEach(({ matchPending, matchRejected }) => {
builder.addMatcher(matchPending, (state) => {
state.error = null;
//I want set the loading state of the user Slice to be true
});
builder.addMatcher(matchRejected, (state, { payload: error }) => {
state.error = error?.data?.message;
//I want set the loading state of the user Slice to be false
});
});
},
});
export const { addMembers } = messageSlice.actions;
export default messageSlice.reducer;
Is there any method such that I can access the loading and error from the user slice and can be set at the messageSlice also? Or should I change the structure of the state or should I duplicate those variables in both slices?

Redux Toolkit: dispatch an action in extraReducers of current slice

I tried this but did not help me. this is my slice:
export const getAll = () => {
return axios.get('http://localhost:4000/students').then(response => {
return response.data
})
}
export const retriveStudents = createAsyncThunk(
"students/retrive", async () => {
return await getAll()
}
)
const studentSlice = createSlice({
name: "student",
initialState,
reducers: {
addNote: (state, action) => {
return { ...state, note: action.payload }
}
},
extraReducers: {
[retriveStudents.fulfilled]: (state, action) => {
studentSlice.actions.addNote("MY DATA...")
return { ...state, items: [...action.payload] }
}
}
})
In extraReducers I want to dispatch addNote action.
I tried the following, but does not work.
extraReducers: {
[retriveStudents.fulfilled]: (state, action) => {
studentSlice.actions.addNote("MY DATA...") // this. line ...
return { ...state, items: [...action.payload] }
}
}

Can't fetchdata with useEffect in a product component

hello everyone I am aking a MERN project and Im running into a peobleme.
so Im using redux toolkit slice and when I try to dispatch the fetchProduct like so
import React, { useEffect, useState } from "react";
import "./products.css";
import { useSelector, useDispatch } from "react-redux";
import { DataGrid } from "#mui/x-data-grid";
import { Link, Navigate } from "react-router-dom";
import DeleteForeverIcon from "#mui/icons-material/DeleteForever";
import Loader from "components/Loader";
import { Button } from "#mui/material";
import { useNavigate } from "react-router-dom";
import Message from "components/Message";
import { fetchProducts } from "slices/productSlice";
const Products = () => {
const dispatch = useDispatch();
const navigate = useNavigate();
const { loading, error, products } = useSelector((state) => state.product);
useEffect(() => {
dispatch(fetchProducts());
}, [dispatch]);
const columns = [
{ field: "_id", hide: true },
{
field: "name",
flex: 1,
headerName: "Product",
width: 200,
renderCell: (params) => {
return (
<div className="productListItem">
<img className="productListImg" src={params.row.image} alt="" />
{params.row.name}
</div>
);
},
},
{ field: "countInStock", headerName: "Stock", flex: 1 },
{
field: "price",
headerName: "Price",
flex: 1,
},
{
field: "brand",
headerName: "Brand",
flex: 1,
},
{
field: "action",
headerName: "Action",
flex: 1,
renderCell: (params) => {
return (
<>
<Link to={"/products/" + params.row._id}>
<button className="productListEdit">Edit</button>
</Link>
<DeleteForeverIcon
className="productListDelete"
// onClick={() => deleteHandler(params.row._id)}
/>
</>
);
},
},
];
return (
<div style={{ height: "90vh" }}>
{loading ? (
<Loader />
) : error ? (
<Message variant="error" />
) : (
<DataGrid
height={100}
getRowId={(row) => row._id}
rows={products?.products}
disableSelectionOnClick
columns={columns}
rowsPerPageOptions={[10, 15, 20]}
pageSize={10}
checkboxSelection
/>
)}
</div>
);
};
export default Products;
the data never get fetched and I get this error in the browser:
"Warning: Failed prop type: The prop rows is marked as required in ForwardRef(DataGrid), but its value is undefined."
but when I remove what's inside the return of the componenet and add just an h1 tag the data get fetched and I can find it in redux store states
this is the slice I am working with :
import axios from 'axios';
import { createSlice } from '#reduxjs/toolkit';
const initialState = {
products: [],
loading: false,
error: null
};
const baseurl = "http://localhost:5001"
const productSlice = createSlice({
name: 'product',
initialState,
reducers: {
fetchProductsStart(state) {
state.loading = true;
},
fetchProductsSuccess(state, action) {
state.products = action.payload;
state.loading = false;
state.error = null;
},
fetchProductsError(state, action) {
state.loading = false;
state.error = action.payload;
},
deleteProductStart(state) {
state.loading = true;
},
deleteProductSuccess(state, action) {
state.products = state.products.filter(product => product._id !== action.payload);
state.loading = false;
state.error = null;
},
deleteProductError(state, action) {
state.loading = false;
state.error = action.payload;
},
updateProductStart(state) {
state.loading = true;
},
updateProductSuccess(state, action) {
const index = state.products.findIndex(product => product._id === action.payload._id);
state.products[index] = action.payload;
state.loading = false;
state.error = null;
},
updateProductError(state, action) {
state.loading = false;
state.error = action.payload;
},
fetchProductStart(state) {
state.loading = true;
},
fetchProductSuccess(state, action) {
state.products = [action.payload];
state.loading = false;
state.error = null;
},
fetchProductError(state, action) {
state.loading = false;
state.error = action.payload;
}
}
});
export const {
fetchProductsStart,
fetchProductsSuccess,
fetchProductsError,
deleteProductStart,
deleteProductSuccess,
deleteProductError,
updateProductStart,
updateProductSuccess,
updateProductError,
fetchProductStart,
fetchProductSuccess,
fetchProductError
} = productSlice.actions;
export default productSlice.reducer
export const fetchProducts = () => async dispatch => {
try {
dispatch(fetchProductsStart());
const response = await axios.get(`${baseurl}/products`);
dispatch(fetchProductsSuccess(response.data));
} catch (error) {
dispatch(fetchProductsError(error.message));
}
};
export const deleteProduct = id => async dispatch => {
try {
dispatch(deleteProductStart());
await axios.delete(`${baseurl}/products/${id}`);
dispatch(deleteProductSuccess(id));
} catch (error) {
dispatch(deleteProductError(error.message));
}
};
export const updateProduct = product => async dispatch => {
try {
dispatch(updateProductStart());
const response = await axios.patch(`${baseurl}/products/${product._id}`, product);
dispatch(updateProductSuccess(response.data));
} catch (error) {
dispatch(updateProductError(error.message));
}
};
so what am I doing wrrong here
the modified Slice
import { createSlice, createAsyncThunk } from '#reduxjs/toolkit';
const initialState = {
products: [],
loading: 'idle',
error: null
};
const baseurl = 'http://localhost:5001';
export const fetchProducts = createAsyncThunk(
'products/fetchProducts',
async () => {
const response = await axios.get(`${baseurl}/products`);
return response.data;
}
);
export const deleteProduct = createAsyncThunk(
'products/deleteProduct',
async id => {
await axios.delete(`${baseurl}/products/${id}`);
return id;
}
);
export const updateProduct = createAsyncThunk(
'products/updateProduct',
async product => {
const response = await axios.patch(
`${baseurl}/products/${product._id}`,
product
);
return response.data;
}
);
export const fetchProduct = createAsyncThunk(
'products/fetchProduct',
async id => {
const response = await axios.get(`${baseurl}/products/${id}`);
return response.data;
}
)
const productSlice = createSlice({
name: 'products',
initialState,
reducers: {},
extraReducers: {
[fetchProducts.pending]: state => {
state.loading = 'pending';
},
[fetchProducts.fulfilled]: (state, action) => {
state.loading = 'idle';
state.products = action.payload;
},
[fetchProducts.rejected]: (state, action) => {
state.loading = 'idle';
state.error = action.error.message;
},
[deleteProduct.pending]: state => {
state.loading = 'pending';
},
[deleteProduct.fulfilled]: (state, action) => {
state.loading = 'idle';
state.products = state.products.filter(product => product._id !== action.payload);
},
[deleteProduct.rejected]: (state, action) => {
state.loading = 'idle';
state.error = action.error.message;
},
[updateProduct.pending]: state => {
state.loading = 'pending';
},
[updateProduct.fulfilled]: (state, action) => {
state.loading = 'idle';
const index = state.products.findIndex(product => product._id === action.payload._id);
state.products[index] = action.payload;
},
[updateProduct.rejected]: (state, action) => {
state.loading = 'idle';
state.error = action.error.message

React - Redux-Toolkit items in cart

I am learning react with redux-toolkit. I am stuck with some actions there.
I want to add quantity in Cart, so if I add same item more than once it should be like X1/x2/x3...etc.
And I want to delete items/item but only with the same ID ( when I click delete only delete that one ex. APPLE)
import { createSlice } from "#reduxjs/toolkit";
const initialState = {
hidden: true,
cartItems: 0,
itemsInCart: [],
quantity: 0,
totalCount: 0,
};
export const cartSlice = createSlice({
name: "cart",
initialState,
reducers: {
removeItem: (state, action) => {},
removeAll: (state) => {
state.cartItems = 0;
state.itemsInCart = [];
state.totalCount = 0;
},
addToCart(state, action) {
state.itemsInCar = state.itemsInCart.push(action.payload);
state.cartItems += 1;
state.totalCount += action.payload.price;
},
showCart: (state) => {
state.hidden = !state.hidden;
},
},
});
export const { showCart, addToCart, removeAll, removeItem } = cartSlice.actions;
export default cartSlice.reducer;
addToCart: (state, action) => {
const itemInCart = state.cart.find((item) => item.id === action.payload.id);
if (itemInCart) {
itemInCart.quantity++;
} else {
state.cart.push({ ...action.payload, quantity: 1 });
}
},

Cannot read property 'endsWith' of undefined - Redux Toolkit testing

I'm having issues while testing a slice with React Testing Library. I was running the following simple test:
import reducer from "states/slices";
test("should return the initial state", () => {
expect(reducer(undefined, {})).toEqual({
loading: true,
libraries: [],
books: [],
error: {
error: false,
variant: "error",
message: "",
},
});
});
The slice under test is the following:
import { createAsyncThunk, createSlice } from "#reduxjs/toolkit";
import { getLibraries, getBooks } from "api";
const initialState = {
loading: true,
libraries: [],
books: [],
error: {
error: false,
variant: "error",
message: "",
},
};
export const fetchLibraries = createAsyncThunk("books/libraries", async () => {
const res = await getLibraries();
return res.data;
});
export const fetchBooks = createAsyncThunk(
"books/books",
async ({ title, libraryId, page }) => {
const res = await getBooks(title, libraryId, page);
return res.data;
}
);
const booksSlice = createSlice({
name: "books",
initialState,
reducers: {
unsetError: (state) => {
state.error = { error: false, variant: "error", message: "" };
},
},
extraReducers: (builder) => {
builder
.addCase(fetchLibraries.fulfilled, (state, action) => {
state.loading = false;
state.libraries = action.payload;
})
.addCase(fetchBooks.fulfilled, (state, action) => {
state.loading = false;
state.books = action.payload;
})
// .addCase(fetchBooks.pending, (state, action) => {
// state.loading = true;
// state.error = { error: false, variant: "error", message: "" };
// })
// .addCase(fetchLibraries.pending, (state, action) => {
// state.loading = true;
// state.error = { error: false, variant: "error", message: "" };
// })
// .addCase(fetchBooks.rejected, (state, action) => {
// state.loading = false;
// state.error.error = true;
// state.error.variant = "error";
// state.error.message =
// "Error. Try again.";
// })
// .addCase(fetchLibraries.rejected, (state, action) => {
// state.loading = false;
// state.error.error = true;
// state.error.variant = "error";
// state.error.message =
// "Error. Try again.";
// });
.addMatcher(
(action) => action.type.endsWith("/pending"),
(state, action) => {
state.loading = true;
state.error = { error: false, variant: "error", message: "" };
}
)
.addMatcher(
(action) => action.type.endsWith("/rejected"),
(state, action) => {
state.loading = false;
state.error.error = true;
state.error.variant = "error";
state.error.message =
"Error. Try again.";
}
);
},
});
const { actions, reducer } = booksSlice;
export const { unsetError } = actions;
export default reducer;
I'm getting back TypeError: Cannot read property 'endsWith' of undefined when running the test with the addMatchers in the slice. If I replace them with the addCases (the commented ones), the test works as expected.
Instead, if I normally launch the application, everything works correctly in either case.
Why does this happen? I am defining wrongly the matchers?
In your test case you are using {} as an action. Therefore when you are checking in the matcher action.type.endsWith() the action.type is not defined.
You can probably fix this if you use action.type?.endsWith in your matcher.

Resources