redux add property to object dynamically - reactjs

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))

Related

How to reuse states in redux-toolkit

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.

State stored in localStorage gets overwritten on page refresh in React redux? How to persist state?

I'm adding user-objects to a users array Like so, users : [{}, {}, .....]. eventually this list of users is rendered to the UI. All fine so far, however every time I refresh the page and want to add a new user-object to the users array it overwrites the existing array and clears the UI. Ofcourse I want the data to persist and stay visible in the UI.
I would like to persist state with react-redux in localStorage to achieve this. The code below shows what I've done so far.
// Store
const reducer = combineReducers({
cats: getCatReducer,
users: registerReducer,
})
const persistedState = loadState();
const initialState = {
cats: {cat: []},
users: {users:[]},
persistedState,
}
const store = configureStore(
{
reducer,
initialState,
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(thunk),
})
store.subscribe(() => {
saveState(store.getState());
});
export default store
//LocalStorage
export const loadState = () => {
try {
const serializedState = localStorage.getItem('state');
if (serializedState === null) {
return undefined;
}
return JSON.parse(serializedState);
} catch (err) {
return undefined;
}
};
export const saveState = (state) => {
try {
const serializedState = JSON.stringify(state);
localStorage.setItem('state', serializedState);
} catch (error) {
// ignore write errors
console.log(error.message)
}
};
//Action
export const register = (user) => (dispatch) => {
dispatch({ type: REGISTER_SUCCESS, payload: user })
}
//Reducer
export const registerReducer = (state = { users: [] }, action) => {
switch (action.type) {
case REGISTER_REQUEST:
return { loading: true };
case REGISTER_SUCCESS:
let user = action.payload
return { ...state, users: [...state.users, user], loading: false };
case REGISTER_FAIL:
return { loading: false, error: action.payload };
default:
return state;
}
}
//screen component
const UserScreen = () => {
const users = useSelector(state => state.users.users)
return (
<>
{users.map((user, index) => {
return (
<div key={user.id} className={styles.userInfo}>
<div className={styles.user}> {user.name}</div>
<div className={styles.user}>{user.address}</div>
<div className={styles.user}>{user.country}</div>
<div className={styles.user}>{user.email}</div>
</div>
)
})}
</>
)
}
//LocalStorage log
JSON.parse(localStorage.getItem('state')) -->
{users: {...}}
{
"users": [
{
"id": "89526872-8b33-46f4-86a2-faa83ae9686f",
"name": "John Doe",
"email": "john#example.com",
"address": "SandyRoad 456",
"country": "USA",
"password": "123"
},
{
"id": "400a4226-c287-415a-b6af-4d119dc75e79",
"name": "Jane Doe",
"email": "jane#example.com",
"address": "Hill 78",
"country": "USA",
"password": "123"
}
],
"loading": false
}
//Redux DevTools (raw) before refreshing the page!
{
cats: {
cat: []
},
users: {
users: [
{
id: '89526872-8b33-46f4-86a2-faa83ae9686f',
name: 'John Doe',
email: 'john#example.com',
address: 'SandyRoad 456',
country: 'USA',
password: '123'
},
{
id: '400a4226-c287-415a-b6af-4d119dc75e79',
name: 'Jane Doe',
email: 'jane#example.com',
address: 'Hill 78',
country: 'USA',
password: '123'
}
],
loading: false
}
}
It seems like you're not initializing your state correct. The persistedState will be another property in your redux state (you should be able to see it in the redux dev tools). What you probably try to achieve is overwriting the empty cats and users properties. Changing
const initialState = {
cats: cat: [],
users: [],
persistedState,
}
to
const initialState = {
cats: cat: [],
users: [],
...persistedState,
}
should work.

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.

When to update state or return action.payload for redux thunk extra reducers?

I understand the first way to update the store, which is concatenating the new post to the existing list of posts. But how does returning the list of users set the state to the list like in the second image? I only ask because it seems I can't achieve the same for users by doing something similar to what was done with posts.
Also here is the link to the redux tutorial: https://redux.js.org/tutorials/essentials/part-5-async-logic
Updating posts list using concat:
updating user list by returning payload
If you want to mutate the whole state, return the data to replace the whole state.
import { createSlice, createAsyncThunk, configureStore } from '#reduxjs/toolkit';
interface User {
name: string;
}
const initialState = [] as User[];
export const fetchUsers = createAsyncThunk('users/fetchUsers', async () => {
return [{ name: 'teresa teng' }];
});
const usersSlice = createSlice({
name: 'users',
initialState,
reducers: {},
extraReducers(builder) {
builder.addCase(fetchUsers.fulfilled, (state, action) => {
// state = action.payload;
return action.payload;
});
},
});
const store = configureStore({ reducer: { users: usersSlice.reducer } });
store.dispatch(fetchUsers());
store.subscribe(() => {
console.log(store.getState());
// { users: [] }, if you use `state = action.payload`
// { users: [ { name: 'teresa teng' } ] }, if you use `return action.payload`
});
If you want to mutate some fields in the state, you can directly assign values (Why? Because RTK use immer underly) ​​to the fields without returning.
builder.addCase(fetchUsers.fulfilled, (state, action) => {
state.status = 'success'
state.posts = state.posts.concat(action.payload)
});

How can I solve this error with Immer - "Uncaught (in promise) TypeError: Cannot perform 'get' on a proxy that has been revoked"

I' like to push the array is from server in empty array in initialState.
As I know to use immer for saving array,
draft.arr.push({a:1, b:2})
But, I don't know why I got the message
Uncaught (in promise) TypeError: Cannot perform 'get' on a proxy that has been revoked
I've tried to use concat and push. But I was not able to get what I want.
const initialState : WorkerState = {
workersList : []
}
const workers = handleActions<WorkerState, any>(
{
[GET_WORKERS_LIST]: (state, action: GetWorkersList) => {
return produce(state, draft => {
action.payload.then((res: WorkerInfo[]) => {
res.map(data => {
return draft.workerList.push({
id: data.id,
name: data.name,
email: data.email,
user_type: data.user_type,
address: data.address,
salary: data.salary,
profile_image: data.profile_image,
birth: data.birth,
join_date: data.join_date,
leave_date: data.leave_date,
working_year: data.working_year,
grade: data.grade,
half_vacation: data.half_vacation,
total_year_vacation: data.total_year_vacation,
year_vacation: data.year_vacation,
});
});
});
});
},initialState,
);
I presume that you use handleAction(s) function of redux-actions package.
It will create multiple redux reducers. Reducers are just pure functions that take the previous state and an action, and return the next state.
You shouldn't include side-effect operations in them, like promise. You should dispatch an action with the data from the API as its the payload. Then you can mutate the immer draft state directly, just replace the workersList directly instead of map + push the item one by one.
E.g.
import produce from 'immer';
import { combineReducers, createStore } from 'redux';
import { createActions, handleActions } from 'redux-actions';
const api = {
async getWorkersList() {
return [
{ id: 1, name: 'a' },
{ id: 2, name: 'b' },
];
},
};
const GET_WORKERS_LIST = 'GET_WORKERS_LIST';
interface WorkerState {
workersList: any[];
}
interface GetWorkersList {
payload: any[];
}
interface WorkerInfo {}
const initialState: WorkerState = {
workersList: [],
};
const workers = handleActions<WorkerState, any>(
{
[GET_WORKERS_LIST]: (state, action: GetWorkersList) => {
return produce(state, (draft) => {
draft.workersList = action.payload;
});
},
},
initialState,
);
const { getWorkersList } = createActions({
GET_WORKERS_LIST: (payload) => payload,
});
const rootReducer = combineReducers({ workers });
const store = createStore(rootReducer);
store.subscribe(() => {
console.log(store.getState().workers);
});
api.getWorkersList().then((res) => {
store.dispatch(getWorkersList(res));
});
Output:
{ workersList: [ { id: 1, name: 'a' }, { id: 2, name: 'b' } ] }

Resources