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?
Related
IMAGE LINK HERE. it doubles the initial return.
import { createAsyncThunk, createSlice } from '#reduxjs/toolkit'
import axios from 'axios'
let url = 'http://localhost:3000/api/v1/posts'
const initialState = {
allPosts: [],
isLoading: false,
isError: false,
}
export const getAllPost = createAsyncThunk('allPosts/getAllPost', async (page, thunkAPI) => {
console.log(page)
try {
const { data } = await axios.get(`${url}?page=${page}`)
return data.posts
} catch (error) {
console.log(error)
}
})
const allPostSlice = createSlice({
name: 'allPosts',
initialState,
reducers: {},
extraReducers: {
[getAllPost.pending]: (state) => {
state.isLoading = true
},
[getAllPost.fulfilled]: (state, { payload }) => {
console.log(payload)
state.allPosts = [...state.allPosts, ...payload]
state.isLoading = false
},
[getAllPost.rejected]: (state) => {
state.isLoading = false
state.isError = true
},
},
})
export default allPostSlice.reducer
When I try to append ...state.allPosts, which is supposed to be the old state, it prints double the initial payload. I only need it printed once, not twice.
[getAllPost.fulfilled]: (state, { payload }) => {
console.log(payload)
state.allPosts = [...state.allPosts, ...payload]
state.isLoading = false
},
How can I print my payload only once using state.allPosts?
In my fullfilled i am getting response as undefined. any one please help?
code :
import { createAsyncThunk, createSlice } from "#reduxjs/toolkit";
import axios from "axios";
const fetchPost = createAsyncThunk('fetch/post', async (params: string) => {
try {
const { data } = await axios.get('https://registry.npmjs.org/-/v1/search', { params: { text: params } })
data.objects.map((result: any) => {
console.log('result', result)//getting result
return result.package.name;
});
} catch (err: any) {
return err?.response;
}
})
interface RepositoriesState {
loading: boolean;
error: string | null;
data: string[];
}
const initialRepoState:RepositoriesState = {
loading: false,
error: null,
data:[]
}
const repositorySlice = createSlice({
name: 'repo-slice',
initialState: initialRepoState,
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchPost.pending, (state) => {
state.loading = true
})
.addCase(fetchPost.fulfilled, (state, action) => {
state.loading = false;
state.data = action.payload;
console.log('payload', action.payload) //undefined
})
.addCase(fetchPost.rejected, (state) => {
state.loading = false;
state.error = "error in api";
state.data = [];
})
}
})
export { fetchPost };
export default repositorySlice.reducer;
Nothing is getting returned from your function... so its undefined
I guess it should be as so
const fetchPost = createAsyncThunk('fetch/post', async (params: string) => {
try {
const { data } = await axios.get('https://registry.npmjs.org/-/v1/search', { params: { text: params } })
return data.objects.map((result: any) => {
console.log('result', result)//getting result
return result.package.name;
});
} catch (err: any) {
return err?.response;
}
})
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;
This is a Redux issue.
Take a look at the logic that dispatched this action: {type: 'users/handleverification/fulfilled', payload: {…}, meta: {…}}
export const UserValidation = createAsyncThunk( //This Function should move to another folder
'users/handleverification',
async(thunkAPI) => {
try{
//Logic
if(Authenticated.status === 201){
localStorage.setItem('token', Authenticated.data.token);
delete Authenticated.token;
return Authenticated
console.log("Test me",Authenticated.data);
}
}
catch(error){
window.alert("error");
if(NewUser){
//delete User????????
}
}
}
)
Here is the Create Slice function:
export const userSlice = createSlice({
name: 'user',
initialState: {
UserData: {},
isFetching: false,
isSuccess: false,
isError: false,
errorMessage: '',
},
reducers: {
clearState: (state) => {
state.isError = false;
state.isSuccess = false;
state.isFetching = false;
return state;
},
},
extraReducers: {
[UserValidation.fulfilled]:(state,{payload}) => {
// console.log("Fullfilled:")
},
[UserValidation.pending]:(state) => {
// console.log("Pending");
},
[UserValidation.rejected]:(state,{payload}) => {
// console.log("Rejected");
},
}
})
Why do I get the error:
A non-serializable value was detected in an action, in the path: payload.config.adapter. Take a Look at the action 'users/handleverification'
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.