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
}
},
Related
I am using little state machine for the state management. I have following state
export const todoState = { todoList: [] }
Now I am calling this using the actions where action is like
export const updateTodoList = (state, payload) => {
return {
...state,
toDoList: {
...state.toDoList,
...payload
}
}
}
calling this action
updateToDoList({ id: '1', text:'11', isComplete: 'false })
But still actions does not update the array of toDoList and also it does not take the previous values into consideration .
Can any one help me with the actions updation code ? Thanks.
1) TYPO: todoList instead of toDoList
2) todoList is an array not an object. So spread it into an array.
3) Since you are passing an object so no need to spread it
export const updateTodoList = (state, payload) => {
return {
...state,
todoList: [
...state.todoList,
payload, // It will add the payload object into todoList
],
};
};
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));
i started to study the react. here is my problem. I have some reducers
let reducers = combineReducers({
user: userReducer,
index_page: indexReducer,
notifications: notificationsReducer
});
Notifications Reducer has its own state of notifications for their renderingand in indexReducer there is. An axios request which, after the response, should draw a notification to the user - change state in notificationsReducer.
I do not quite understand how to do this.
This is my code:
notificationsReducer
let initialState = [
{id: 3, text: 'test_msg', state: 'error'}
];
export const createNotificationActionCreator = (msg_text, msg_state) => {
return {
type: 'SHOW_NOTIFY',
msg_text: msg_text,
msg_state: msg_state
}
}
const notificationsReducer = (state = initialState, action) => {
switch (action.type) {
case SHOW_NOTIFY:
let msg = {
text: action.msg_text,
msg_state: action.msg_state
};
state.push(msg);
break;
}
return state;
}
indexReducer
const indexReducer = (state = initialState, action) => {
switch (action.type) {
case CREATE_NEW_BET:
let bet_data = new Object();
bet_data.bet = state.betAmount;
bet_data.color = action.color;
axios.get('http://localhost/createbet', {
params: {
bet_data
}
}).then(function (response) {
// CHANGE STATE IN notificationsReducer
});
break;
}
return state;
}
To update state in another reducer, I would suggest dispatching the SHOW_NOTIFY action right after dispatching the CREATE_NEW_BET. This can be done using Redux Thunks.
Also read this Stack Overflow answer on suggestions to update state managed by another reducer: Updating state managed by another reducer
With redux-thunk setup, this is what your thunk would look like:
const createBetAndNotify = () => (dispatch) => {
return dispatch({ type: "CREATE_NEW_BET" }).then(() => {
dispatch({ type: "SHOW_NOTIFY" })
})
}
Then inside your React component, you would dispatch the above thunk:
dispatch(createBetAndNotify());
If I fetch this array of restos with redux:
[{
res_id: Int,
res_name: String,
res_category: String,
res_category_id: Int,
city_id: Int
}]
My action looks something like this:
export const getrestos = () => {
const resData = await response.json();
dispatch({
type: GET_RESTOS,
payload: resData
});
};
};
export const setFilters = filterSettings => {
console.log(filterSettings);
return { type: SET_FILTERS, filters: filterSettings };
};
And this is my reducer:
import { GET_RESTOS, SET_FILTERS } from '../actions/restos';
const initialState = {
restoList: [],
filteredRestos: []
};
export default (state = initialState, action) => {
switch (action.type) {
case GET_RESTOS:
return {
restoList: action.payload
}
case SET_FILTERS:
const appliedFilters = action.filters;
const updatedFilteredRestos = state.restoList.filter(resto => {
if (appliedFilters.cityID || resto.city_id) {
resto => resto.city_id.indexOf(cityID) >= 0
return { ...state, filteredRestos: updatedFilteredRestos };
}
});
return { ...state, filteredRestos: updatedFilteredRestos };
default:
return state;
}
};
I have touchable categorys in a page, and when i touch one i want to fetch the corresponding restos for that category and show them in a flatlist. Apart from that i want to have a search bar that when I type I want to show restos by res_name and/or by res_category.
Ive tried to create selectors, but I dont understand how, i dont need an specific approach, but the most clean or efficient as possible.
Thanks in advance if anyone can give me a hint or solution!
EDIT
The problem is im getting undefined in updatedFilteredRestos.
Your reducers should be clean, dumb and all they do should be returning objects. This makes your components more testable and errors easier to catch. In my opinion, this is a perfect use-case for reselect. Here's a medium article: https://medium.com/#parkerdan/react-reselect-and-redux-b34017f8194c But the true beauty of reselect is that it will memoize for you, i.e. if your states don't change, it uses a cached version of the data.
Anyway, you should clean up your restoReducer to something to this effect.
import { GET_RESTOS, SET_FILTERS } = "../actions/restos";
const initialState = {
restoList: [],
filteredRestos: []
};
const restoReducer = (state = initialState, action) => {
switch(action.type) {
case GET_RESTOS:
return { ...state, restoList: action.payload };
case SET_FILTERS:
return { ...state, filteredRestos: action.payload };
default:
return state;
}
}
Then write your filtered resto selector:
// ../selectors/restos
import { createSelector } from "reselect";
// First, get your redux states
const getRestos = (state) => state.restos.restoList;
const getFilteredRestos = (state) => state.restos.filteredRestos;
// Next, create selectors
export const getFilteredRestoList = createSelector(
[getRestos, getFilteredRestos],
(restoList, filteredRestos) => {
// need to check for non-empty filters
// if it is, simply return the unfiltered `restoList`
if(!Array.isArray(filteredRestos) || !filteredRestos.length)
return restoList || [];
// If you do have valid filters, return filtered logic
return restoList.filter(r => filteredRestos.some(f => f.cityID === r.city_id));
);
Then, use this selector in your components:
// ../components/my-app
import { getFilteredRestoList } from "../selectors/restos";
// hook it up to your `mapStateToProps` as you would a normal state
// except this time, it's a special selector
const mapStateToProps = (state, ownProps) => {
restoList: state.restos.restoList,
filteredRestos: state.restos.filteredRestos,
filteredRestoList: getFilteredRestoList(state) //<-- this is your selector
}
Then inside your component, just reference it: this.props.filteredRestoList.
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.