How to reuse states in redux-toolkit - reactjs

I am working on Redux-ToolKit to build an app that fetches the data from an API using. In my Redux store I have multiple slices and I would like to access the search state of the searchSlice inside the bookingSlice.Is there any way to solve this issue? I haven't been able to find a solution yet.
I am looking for best practice
import { createSlice } from "#reduxjs/toolkit";
const initialState = {
destination: "",
checkIn: new Date(),
checkOut: new Date(),
count: {
adults: 2,
children: 0,
rooms: 1,
},
price: {
min: 0,
max: 500000,
},
};
const searchSlice = createSlice({
name: "search",
initialState,
reducers: {
setDateRange: (state, { payload }) => {
const { startDate, endDate } = payload;
state.checkIn = startDate;
state.checkOut = endDate;
},
increment: (state, { payload }) => {
state.count[payload] += 1;
}
}
export default searchSlice.reducer;
Booking slice
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
import axios from "axios";
const initialState = {
search: ?
bookings: [],
isLoading: false,
isSuccess: false,
isError: false,
message: "",
};
export const createBooking = createAsyncThunk(
"booking/create",
async (state, { rejectWithValue }) => {
try {
const { data } = await axios.post("api/bookings", { state });
return data;
} catch (error) {
if (error.response && error.response.data.message) {
return rejectWithValue(error.response.data.message);
} else {
return rejectWithValue(error.message);
}
}
}
);
const bookingSlice = createSlice({
name: "booking",
initialState,
reducers: {},
}

You cannot access the state of another reducer inside a reducer. But you can use getState() inside the createBooking action to get the current state tree of your application. Then filter what information you need from the searchSlice and return it from the action along with the api response.

Related

State value not change when dispatch to another reduces in redux

I am building login system. I am having problem with store state value when I use Selector function. in my system, I want to get isSuccess, error, userInfo from store state values
/login/index.js
const userLogin = useSelector((state) => state.userLogin);
console.log(userLogin);
const { isSuccess, error, userInfo } = userLogin;
if error it will show error message on UI, otherwise if isSuccess it will show success message. isSuccess appears when post to api success (it mean user login success) and user data is dispatched to loginSuccess reduces else error value dispatched to loginFail reduces
/action/userAction.js
import {
loginSuccess,
loginFail,
} from "\~/redux/user/userLoginSlice";
export const login = (inputs) =\> async (dispatch) =\> {
try {
const {data} = await request.post("/auth/login", inputs);
dispatch(loginSuccess(data));
localStorage.setItem("userInfo", JSON.stringify(data));
} catch (error) {
console.log(error.response.data)
dispatch(loginFail(error.response.data))
}
. however instead of just getting isSuccess in loginSucces reduce, it also get error when previous login was loginFail
console.log(userLogin). it should only show userInfo and isSuccess. anyone help me to solve this problem with . this is reduces
userLoginSlice.js
import { createSlice } from '#reduxjs/toolkit';
const userLoginSlice = createSlice({
name: 'userInfo',
initialState: {},
reducers: {
loginSuccess: (state, action) => {
state.isSuccess = true;
state.userInfo = action.payload;
},
loginFail: (state, action) => {
state.error = action.payload
},
},
});
export const { loginSuccess, loginFail } = userLoginSlice.actions;
export default userLoginSlice.reducer;
and my reduce store
store.js
import { configureStore } from '#reduxjs/toolkit';
import productModalReducer from './product-modal/productModalSlice';
import cartItemsSlice from './cart/cartSlide';
import userLoginSlice from './user/userLoginSlice.js';
import userRegisterSlice from './user/userRegisterSlice.js';
import userUpdateSlice from './user/userUpdateSlice.js';
const userInfoFromStorage = localStorage.getItem('userInfo') ? JSON.parse(localStorage.getItem('userInfo')) : null;
const initialState = {
userLogin: { userInfo: userInfoFromStorage },
};
export const store = configureStore({
reducer: {
productModal: productModalReducer,
cartItems: cartItemsSlice,
userLogin: userLoginSlice,
userRegister: userRegisterSlice,
userUpdate: userUpdateSlice,
},
preloadedState: initialState,
});
State value not change when dispatch to another reduces in redux
It seems that this could be because loginSuccess is only adding userInfo and isSuccess to the state but not omitting previous error.
Perhaps try reset state value to exclude error in the reducer:
const userLoginSlice = createSlice({
name: "userInfo",
initialState: {},
reducers: {
loginSuccess: (state, action) => {
// 👇 Reset state to omit error message in loginSuccess
state = { isSuccess: true, userInfo: action.payload };
},
loginFail: (state, action) => {
// 👇 Could add "isSuccess: false, userInfo: null" if needed
state = { error: action.payload };
},
},
});
Or return a state value instead of using Immer proxy state:
const userLoginSlice = createSlice({
name: "userInfo",
initialState: {},
reducers: {
loginSuccess: (state, action) => {
// 👇 Reset state to omit error message in loginSuccess
return { isSuccess: true, userInfo: action.payload };
},
loginFail: (state, action) => {
// 👇 Could add "isSuccess: false, userInfo: null" if needed
return { error: action.payload };
},
},
});

Trying to pass action.payload from one slice to another different slice

What I am trying to achieve is sending action payload from one slice to another and I have been stuck several hours trying to do so.
I have tried accessing the global store but the problem is I am getting errors on doing so
I am using redux-tool-kit to manage the state of my react application and I am trying to pass a payload from one slice to another, the following is my first slice:
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
import axios from 'axios';
import { clearAlert, displayIncorrectEmail } from "./features.js/Alert";
const initialState = {
user: user ? JSON.parse(user) : null,
isMember: false,
isLoading: true
}
This section still for the first slice
export const getRegisteredUser = createAsyncThunk('auth/getRegistrationRes', async (currentUser, thunkAPI) => {
try {
const response = await axios.post('/api/v1/auth/register', currentUser)
return response.data
} catch (error) {
// console.log(error.message)
thunkAPI.rejectWithValue(error.message)
}
})
export const getLoginUser = createAsyncThunk('auth/getLoginRes', async (currentUser, thunkAPI) => {
try {
const response = await axios.post('/api/v1/auth/login', currentUser)
thunkAPI.dispatch(displaySuccess())
setTimeout(() => {
thunkAPI.dispatch(clearAlert())
}, 3000);
return response.data
} catch (error) {
thunkAPI.dispatch(displayIncorrectEmail())
// console.log(error.response.data.msg);
thunkAPI.rejectWithValue(error.message)
//the below return is the action-payload I want to pass to another slice
return error.response.data.message
//
}
})
const authenticationSlice = createSlice({
name: 'auth',
initialState,
reducers: {
},
extraReducers: {
// login user reducers
[getLoginUser.pending]: (state) => {
state.isLoading = true;
},
[getLoginUser.fulfilled]: (state, action) => {
state.isLoading = false;
// console.log(action.payload.getState());
// action.payload.load = true
state.user = action.payload.user
},
[getLoginUser.rejected]: (state) => {
state.isLoading = false;
state.user = null
},
}
})
export const { registerUser, loginUser } = authenticationSlice.actions
export default authenticationSlice.reducer
This is the second slice is the code below
import { createSlice } from "#reduxjs/toolkit";
const initialState = {
showAlert: false,
alertText: '',
alertType: '',
}
const alertSlice = createSlice({
name: 'alert',
initialState,
reducers: {
displayIncorrectEmail: (state, action) => {
state.showAlert = !state.showAlert
//I want to pass the action.payload to this instead of hard-coding it to 'incorrect email' //below
state.alertText = 'incorrect email'
//the state.alertText above
state.alertType = "danger"
},
clearAlert: (state) => {
// setTimeout(() => {
state.showAlert = !state.showAlert;
// }, 4000);
}
}
})
export const { displayDanger, clearAlert, displaySuccess, displayIncorrectEmail } = alertSlice.actions
export default alertSlice.reducer
Kindly help if you have an idea on how to sort this.
cheers.
Just add an extraReducer for getLoginUser.rejected to the second slice as well. You can add that to the extraReducers of as many slices as you want to.
By the way, you really should not be the map object notation of extraReducers, but the extraReducers "builder callback" notation. The object notation you are using is soon going to be deprecated and we have been recommending against it in the docs for a long time.

How to run async call on a reducer?

import { createSlice, PayloadAction } from "#reduxjs/toolkit"
import { Dispatch } from 'redux';
import axios from "axios"
const API_URL = process.env.REACT_APP_API_HOST_URL || ""
export type projectObj = {
id?: number
createdBy?: number,
title: string,
description: string,
endDate: string,
priority: 'Critical' | 'High' | 'Medium' | 'Low',
status: 'Not Active' | 'In Progress' | 'Completed',
progress: number,
favorite: boolean
}
interface projectState {
projects: projectObj[],
projectFetching: boolean
}
const initialState : projectState = {
projects : [],
projectFetching: false
}
export const projectSlice = createSlice({
name: 'projectReducer',
initialState,
reducers: {
/* errors here */
create: async (state, action : PayloadAction<projectObj>) => {
const projectObj = action.payload
state.projects.push(await createProject(projectObj))
}
},
})
// CREATE PROJECT
const createProject = async (projectObj : projectObj) : Promise<projectObj> => {
try {
const project : projectObj = await axios.post(`${API_URL}/api/projects`, projectObj)
return project
} catch (err : any) {
return projectObj
}
}
export const { create } = projectSlice.actions
export default projectSlice.reducer
Create takes in a projectObj with the props list above and my api will create a new project and then return the project object with id in it. I want to push that into my state.
This errors in the create action. The function createProject returns a promise that I need to await on. . What is the proper way to go about this ?
Edit to ask question about answer-
export const projectSlice = createSlice({
name: 'projectReducer',
initialState,
reducers: {
create: (state, action: PayloadAction<projectObj>) => {
const projectObj = action.payload
state.projects.push(projectObj)
}
},
})
export const createProject = (projectObj: projectObj) => async (dispatch: Dispatch) => {
try {
const response = await axios.post(`${API_URL}/api/projects`, projectObj)
const data: projectObj = response.data.project
dispatch(create(data))
} catch (err: any) {
console.log(err)
}
}
Handle Async Requests with the createAsyncThunk.
const createProjectThunk = createAsyncThunk(
"project/createNew",
async (projectObj: projectObj) => {
const response = await createProject(projectObj);
return response;
}
);
export const projectSlice = createSlice({
name: "projectReducer",
initialState,
reducers: {
/* errors here */
},
extraReducers: (builder) => {
// Add reducers for additional action types here, and handle loading state as needed
builder.addCase(createProjectThunk.fulfilled, (state, action) => {
// Add user to the state array
state.projects.push(action.payload);
});
}
});

redux add property to object dynamically

Hi i have a reducer containing one array of objects. These objects have some properties. When i dispatch an action in redux using useDispatch Hook i want to add some more properties dynamically.How can i achive this thing
//reducer data
users: [
{
id: 1,
first_name: "JACKILINE",
status: "online",
},
{
id: 2,
first_name: "BRONNNZE",
status: "offline",
},
];
I want to add these two properties dynamically mesg: "how are you",lastSeenDate: "30/11/19", How can i update the state in reducer
//i want the reducer like this after dispatching an action
users: [
{
id: 1,
first_name: "JACKILINE",
status: "online",
mesg: "how are you",
lastSeenDate: "30/11/19",
},
{
id: 2,
first_name: "BRONNNZE",
status: "offline",
mesg: "how are you",
lastSeenDate: "30/11/19",
},
],
`
//My action
export const setLastMessage = (payload) => ({
type: actionTypes.SET_LAST_MESSAGE,
payload: {
id: payload.id,
lastSeenDate: payload.date,
mesg:payload.message
},
});
I am not sure what you exactly want to do, but I guess you could do it when you get users rather when you import it.
How do you get users?
Please try to do this in your slice file(maybe src/slices/user.js).
const initialState = {
users: []
}
const slice = createSlice({
name: 'user',
initialState,
reducers: {
getUsers(state, action) {
const { users } = action.payload;
state.users = users.map(user => ({
...user,
mesg: "how are you",
lastSeenDate: "30/11/19"
}))
},
}
});
export const reducer = slice.reducer;
export const getUsers = () => async (dispatch) => {
const response = await axios.get('/api/users');
dispatch(slice.actions.getUsers(response.data));
};
Or you could do the similar thing while importing users in your component.
Hope this would be helpful for you.
You have to add action creator:
export const ANY_ACTION = 'ANY_ACTION';
function actionFun (msg, lastSeenDate) {
return {
type: ANY_ACTION,
msg,
lastSeenDate
}
}
export function handleActionFun (msg, lastSeenDate) {
return (dispatch) => {
return dispatch(actionFun(msg, lastSeenDate))
}
}
In Reducer:
import { ANY_ACTION } from './actionCreators';
export default function users (state = [], action) {
switch (action.type) {
case ANY_ACTION :
return state.map(u => {
return u.mesg = action.msg, u.lastSeenDate = action.lastSeenDate;
})
default :
return state
}
}
You can call it using dispatch(handleActionFun(msg, lastSeenDate))

react reducer toolkit dispatch not working

I wrote profile page I want to get profile datas from api but my dispatch not working, in getProfileDetails function dispatch not working.
import {createSlice} from "#reduxjs/toolkit";
import axios from "../components/axios";
import urls from "../components/Urls";
import authHeader from "../components/authHeader";
const profileSlice = createSlice({
name: 'profile',
initialState: {
profileDetails: [],
},
reducers: {
setProfileDetails: (state, action) => {
state.profileDetails = action.payload;
//return { profileDetails: action.payload };
},
}
});
export const getProfileDetails = (dispatch) => {
axios.get(urls.user_details, { headers: authHeader() }).then((response) => {
dispatch(setProfileDetails(response.data.results[0]));
}).catch(e => {
console.log(e);
});
}
export const { setProfileDetails } = profileSlice.actions;
export default profileSlice.reducer;
A reducer must always return a new object, so your commented version was actually better. Better still would be something like this:
const profileSlice = createSlice({
name: 'profile',
initialState: {
profileDetails: [],
},
reducers: {
setProfileDetails: (state, action) => ({ ...state, profileDetails: action.payload }),
}
});

Resources