How to update multiple form values with one reducer action? - javascript

I am building a multi step form and I am using react-hook-form and redux-toolkit.
Everything works, but on some pages I have multiple input elements (ex. goal and height).
My current code:
const rootSlice = createSlice({
name: "root",
initialState: {
goal: "",
height: "",
age: "",
username: "",
},
reducers: {
chooseGoal: (state, action) => {
state.goal = action.payload;
},
chooseActivityLevel: (state, action) => {
state.activity_level = action.payload;
},
chooseYourAge: (state, action) => {
state.age = action.payload;
},
chooseYourUserName: (state, action) => {
state.username = action.payload;
},
},
});
const goal = useSelector((state) => state.goal);
const { register, handleSubmit } = useForm({
defaultValues: { goal, activity_level },
});
const onSubmit = (data) => {
dispatch(chooseGoal(data.goal));
dispatch(chooseActivityLevel(data.activity_level));
};
I was wondering if I can hook multiple form values to one reducer something like this:
chooseGoalAndHeight: (state, action) => {
state.goal && state.height = action.payload;
},
// And dispatch it like this:
dispatch(chooseGoalAndHeight(data.goal, data.height));
Also is it okay to initialize a form element that will have a number input with empty string?

Good question. This is one of the three main problems in the redux world. See Redux Problems
You can solve that problem clearly and beautifully, using redux-cool lib which has Global and local Actions capability.

Here is how I would do it where I changed the initialState to be an array of the objects you want to add from the form instead of just an object as you probably want to store multiple instances of the form data. Then I combined all the reducers into a single reducer and added in a prepare callback which prepares the action.payload to ensure it is the same format as the object of the initialState. You can read more about Preparing Action Payloads here.
const rootSlice = createSlice({
name: "root",
initialState: [
{
goal: "",
height: "",
age: "",
username: "",
},
],
reducers: {
submitForm: {
reducer(state, action) {
state.push(action.payload);
},
prepare(goal, activity_level, age, username) {
return {
payload: {
goal,
activity_level,
age,
username,
},
};
},
},
},
});
Then you would use the reducer like so just passing in properties of the form as per the initialState objects structure.
dispatch(submitForm(data.goal, data.activity_level, data.age, data.username));

You can try this method. I recently implemented it.
const initialState = {
data: {},
};
const rootSlice = createSlice({
name: 'currentUserDetails',
initialState,
reducers: {
addUserInfo: {
reducer(state, action) {
state.data = {
...state.data,
...action.payload
};
},
},
},
});
export const {
addUserInfo
} = currentUserDetails.actions;
export default rootSlice.reducer;
// Inside your Component
const data = {
goal,
activity_level
};
//dispatch
dispatch(addUserInfo(data));

Related

Dispatch is working but it is not updating the state

I am working on a application which uses redux for state management. There, at a some condition, I want to update the state.
My initial state and reducer function looks like this:
import { createSlice } from '#reduxjs/toolkit';
const filterDataTemplate = {
programId: '',
year: '',
};
const initialState = {
//some other state
filterData: { ...filterDataTemplate },
};
const slice = createSlice({
name: 'editFilterSlice',
initialState: initialState,
reducers: {
updateFilterProgramId: (state, action) => {
state.filterData.programId = action.payload;
},
updateFilterYear: (state, action) => {
state.filterData.year = action.payload;
},
},
});
export const {
updateFilterYear,
updateFilterProgramId,
} = slice.actions;
export default slice.reducer;
So filter details containg year and programId is obtained with the help of this code:
const filterDetails = useAppSelector(
(state) => state.locationsFilter.filterData
);
Let's say I have filter data initially:
filterDetails: {year:2021, programId: "Ameria"}
And i want to have my new filter data to be
filterDetails: {year: "", programId: "Ameria"}
So for this what I am doing:
const handleDelete = (e) => {
e.preventDefault();
if (//some condition) {
console.log("delete is called");
dispatch(updateFilterYear(''));
} else {
dispatch(updateFilterProgramId(''));
}
}
handleDelete function is getting called properly when I am clicking a button because I am getting value inside console.
But after running this code my filter data is not updating. I am not sure what I am doing wrong.
Please help with this.
Action.payload is of object type. So You should reference action.payload.year.
I hope this example will be of any use
​    ​setTodoDate​: ​{
​      ​reducer​: ​(​state​,​ ​action​: ​PayloadAction​<​TodoDate​>​)​ ​=>​ ​{
​        ​state​.​currentDate​ ​=​ ​action​!​.​payload​.​date​;
​      ​}​,
​      ​prepare​: ​(​value​)​ ​=>​ ​(​{
​        ​payload​: ​{​ ...​value​ ​}​,
​      ​}​)​,
​    ​}
I think the issue is because you are trying to mutate your state directly. This is bad practice, and Redux state (and more generally react) is intended to be immutable. Reducers should return a copy of the state, along with the updated values. Documentation linked below.
Redux Documentation
Try writing your reducers like the following
updateFilterYear: (state, action) => {
return {
...state,
filterData: {
...state.filterData,
year: action.payload
}
},
updateFilterProgramId: (state, action) => {
return {
...state,
filterData: {
...state.filterData,
programId: action.payload
}
},

what is the proper way to use redux toolkit?

I`ve just started to learn redux/toolkit. And I encounter the problem. I read in the documentation of redux that async logic can be processed in midlewares which are created with createAsyncThunk.
So I see two ways how the data can be fetched and pushed to the server. First, you can create reducers that are making changes to the state and fetch or push requests directly from the react component calling dispatch the action and just passing payload that is processed by reducers.
and the second way to use thunks and extra reduces. But when I use them I don't have the logic to put to reducers. I wonder which option to use?
const postsSlice = createSlice({
name: 'posts',
initialState: {
postsList: [],
status: 'idle',
error: null
},
reducers:{
addPost: (state, action) => {
return state.postsList.push(action.payload);
},
deletePost: (state, action) => {
return state.postsList.filter((item, i) => i!== action.id);
}
},
extraReducers: {
[fetchPosts.pending]: state => {
state.status = 'loading';
},
[fetchPosts.fulfilled]: (state, action) => {
state.status = 'succeeded';
state.postsList = action.payload;
},
[fetchPosts.rejected]: (state, action) => {
state.status = 'failed';
state.error = action.error.message;
},
}
});
export default postsSlice.reducer;

How to use single action type in different reducer function with createSlice method in redux-toolkit

Is it possible that multiple reducer (which is created with createSlice method) must respond to the same action?
import { createSlice } from '#reduxjs/toolkit';
const isAuthenticated = createSlice({
name: 'isAuthenticated',
initialState: false,
reducers: {
loginSuccess(state, action) {
return true;
},
logout(state, action) {
return false;
},
},
});
export const { loginSuccess, logout } = isAuthenticated.actions;
export default isAuthenticated.reducer;
const currentUser = createSlice({
name: 'currenUser',
initialState: 'jhon',
reducers: {
loginSuccess(state, action) {
return 'steve';
},
logout() {
return state;
},
},
});
export const currentUserReducer = currentUser.reducer;
As You can see action.type loginSuccess is in two different reducers since i am only exporting loginSuccess of isAuthenticated i can use only that action to dispatch.
i know i can export loginSuccess from currentUser as well but i want to dispatch only one action and change the state in two different states.
I know this can be done with vanilla redux and also redux recommend using it Here
In short i am trying to replicate this but with createSlice method in redux-tool-kit.
Thanks in advance for helping.
You are looking for extraReducers:
const isAuthenticated = createSlice({
name: 'isAuthenticated',
initialState: false,
reducers: {
loginSuccess(state, action) {
return true;
},
logout(state, action) {
return false;
},
},
});
export const { loginSuccess, logout } = isAuthenticated.actions;
export default isAuthenticated.reducer;
const currentUser = createSlice({
name: 'currenUser',
initialState: 'jhon',
reducers: {
logout() {
return state;
},
},
extraReducers: builder => {
builder.addCase(isAuthenticated.actions.loginSuccess, () => {
return 'steve'
})
}
});

When an action is dispatched, state of another reducer is removed. Why?

I use Redux in my project for first time. I have multiple reducers and and actions. When the first action is dispatched, state is changed. It looks okey. After dispatching second action, state is changed again but the previous changes are removed. I mean, when 'FETCH_COMPANY_INFORMATIONS' is dispatched companyName is changed and companyDesc set to initial value. Then 'FETCH_INITIAL_MEMBER' is dispatched and companyName is removed but companyDesc is still there and member payloads are also changed. What is my mistake? Thanks.
I tried many ways to solve this but still continue. I check this on Redux DevTools.
memberReducer
const initialState = {
username: '',
companyId: '',
isAdmin: '',
photo: '',
};
export default (state = initialState, action) => {
switch (action.type) {
case FETCH_INITIAL_MEMBER:
return {
...state,
username: action.payload.username,
companyId: action.payload.companyId,
isAdmin: action.payload.isAdmin,
};
default:
return state;
}
};
companyReducer
const initialState = {
companyName: 'companyName',
companyDesc: 'companyDesc',
};
export default (state = initialState, action) => {
switch (action.type) {
case FETCH_COMPANY_INFORMATIONS:
return {
...state,
companyName: action.payload.companyName,
};
default:
return state;
}
};
memberAction
const fetchInıtıalMember = async muuid => {
axios
.get(`/api/member/${muuid}`)
.then(response => {
const username = response.data.mname;
const isAdmin = response.data.misAdmin;
const companyId = response.data.cid;
store.dispatch({
type: FETCH_INITIAL_MEMBER,
payload: {
username,
isAdmin,
companyId,
},
});
})
.catch(error => {});
};
companyAction
const fetchCompanyInformations = () => {
store.dispatch({
type: FETCH_COMPANY_INFORMATIONS,
payload: { companyName: 'dispacthedCompanyName' },
});
};
Edit:
The code above is correct. My mistake is about importing the constants. This Redux implementation works well. I was storing all action type constant in a types.js file. I import this type constants in the another files wrongly. After changing it my problem is solved.

React/Redux Unable to keep previous state of search input

I am trying to save recent searches in local storage.
Reducer.js
const initialState = {
venues: [],
searches: [],
};
const venueReducer = (state = initialState, action) => {
switch (action.type) {
case RECENT_SEARCHES:
return {
...state,
searches: [
...state,
{ near: action.payload.location, query: action.payload.place },
],
};
}
};
Action.js
export const fetchVenues = (place, location) => dispatch => {
dispatch({ type: FETCH_VENUE_REQUESTED })
dispatch({ type: RECENT_SEARCHES, payload: { place, location } })
};
Right now I am getting the user input and saving it to the searches array in reducer but it's not keeping the previous state. Only shows the current user input.
When you spread the searches, you need to do:
[ ...state.searches,
Instead of just:
[ ...state,
As that will put state.venues (Array) and state.searches (Array) in your state.searches

Categories