My reducer keeps on overwriting the value - reactjs

I have a reducer that concats a base price when you click on a menu item, then after when the user clicks on a modifier for that menu item its supposed to add a modifier object to the state but instead it just returns the mod_price object only. I am looking to have it return both the base_price and the mod_price, but instead it returns the mod_price object. Below is my code. Any help would be really appreciated.
/*This shows the cart price for only the selected item next to where it says add to cart*/
export const cartPriceForSelectedItem = (state = [],action) => {
const {payload,type} = action;
switch (type) {
case SELECTED_PRICE :
const {base_price} = payload;
return state.concat({base_price: base_price});
case SET_MOD_PRICE_TOTAL :
return state.concat({mod_price: payload})
}
return state;
}

If state is an object, you are looking for spread operator or Object.assign to return the new state with the merged properties.
return {
...state,
base_price: base_price
};
Object.assign
return Object.assign({}, state, {
base_price: base_price
});
One thing to note is that these operations perform a shallow merge, which I presume is not an issue in this context.
const initialState = [];
const SELECTED_PRICE = 'SELECTED_PRICE';
const SET_MOD_PRICE_TOTAL = 'SET_MOD_PRICE_TOTAL';
const cartPriceForSelectedItem = (state = [], action) => {
const {
payload,
type
} = action;
switch (type) {
case SELECTED_PRICE:
const {
base_price
} = payload;
return state.concat({
base_price: base_price
});
case SET_MOD_PRICE_TOTAL:
return state.concat({
mod_price: payload
})
}
return state;
}
const action1 = {
type: SELECTED_PRICE,
payload: {
"base_price": 20
}
};
const state1 = cartPriceForSelectedItem(initialState, action1);
console.log('state1', state1);
const action2 = {
type: SET_MOD_PRICE_TOTAL,
payload: 45
};
const state2 = cartPriceForSelectedItem(state1, action2);
console.log('state2', state2);

Related

How to access the updated value in my store immediately after updating it?

I have a React application that is currently using Redux for state management.
What I am trying to achieve: Click a Buy Now button - dispatch a action that makes a request to the server to add the item (increment the cart item count based on server response), check the state to see if the cart item count is greater than 0 & do something if it is.
For some reason, I have to click the button twice in order for the cartItemCount to reflect 1?
My current implementation looks like the below (I have tried to pull out all the unrelated code due to the file being quite large):
CourseSpecificScreen.tsx
const mapStateToProps = (state: RootState) => {
return {
courseSpecificReducer: state.courseSpecificReducer,
authState: state.authReducer,
currencyState: state.currencyReducer,
cartReducer: state.cartReducer,
courseCategoriesState: state.courseCategoriesReducer,
};
};
const mapDispatchTopProps = (dispatch: Dispatch<AnyAction>) => {
return bindActionCreators(ActionCreators, dispatch);
};
const connector = connect(mapStateToProps, mapDispatchTopProps);
type CourseSpecificScreenNavigationProp = CompositeNavigationProp<
StackNavigationProp<ExploreRouteStackParamList, "CourseSpecificScreen">,
CompositeNavigationProp<
StackNavigationProp<AppRouteHeaderParamList>,
StackNavigationProp<AuthRouteStackParamList>
>
>;
type CourseSpecificScreenRouteProp = RouteProp<
ExploreRouteStackParamList,
"CourseSpecificScreen"
>;
type Props = PropsFromRedux & {
navigation: CourseSpecificScreenNavigationProp;
route: CourseSpecificScreenRouteProp;
};
type State = {
cartItemCount: number;
};
class CourseSpecificScreen extends Component<Props, State> {
pruchaseItem = async () => {
const {
courseSpecificReducer,
clearCartAndAddItem,
navigation,
cartReducer,
getCartItemCount,
} = this.props;
const paymentMethod = paymentMethodForDevice();
await clearCartAndAddItem(
paymentMethod,
courseSpecificReducer.productData.code as string,
1,
navigation
)
if(cartReducer.cartItemCount > 0) {
// do some stuff
}
};
render() {
return (
<Button
btnStyle={[this.getStyles().smallButtonBuyCourse]}
labelStyle={[this.getStyles().buttonStickyLabelStyle]}
label={translate(
productData.isBundle && productData.isBundle === true
? "CategorySpecificScreen_buyThisBundle"
: "CategorySpecificScreen_buyThisCourse",
)}
onPress={this.purchaseItem}
disabled={false}
/>
)
};
CourseSpecificScreen.contextType = LocalizationContext;
export default connector(CourseSpecificScreen);
ThunkActions.ts
export const clearCartAndAddItem = (
paymentMethod: string,
productCode: string,
quantity: number,
navigation: any,
): AppThunk => {
return async (dispatch) => {
dispatch(cartActions.updateCartLoadingStatus(true));
const response = await cartServices.clearCart();
const {httpStatusCode} = response as APIResponse;
switch (httpStatusCode) {
case httpStatusCodes.SUCCESS_OK:
case httpStatusCodes.SUCCESS_CREATED:
case httpStatusCodes.SUCCESS_NO_CONTENT:
dispatch(cartActions.updateCartLoadingStatus(false));
dispatch(cartActions.updateCartItemCount(0))
globalConfig.setCartItemCount(0);
dispatch(addItemToCart(paymentMethod, productCode, quantity, navigation));
break;
case httpStatusCodes.CLIENT_ERROR_UNAUTHORIZED:
case httpStatusCodes.SERVER_ERROR_INTERNAL_SERVER_ERROR:
dispatch(cartActions.updateCartLoadingStatus(false));
let alertMessage = "Error, please try again later.";
if (response?.message) alertMessage = response?.message;
Alert.alert("Alert", alertMessage, [
{
text: "Ok",
},
]);
break;
default: {
dispatch(cartActions.updateCartLoadingStatus(false));
}
}
};
};
export const addItemToCart = (
paymentMethod: string,
productCode: string,
quantity: number,
navigation: any,
): AppThunk => {
return async (dispatch) => {
dispatch(cartActions.updateCartLoadingStatus(true));
const response = await cartServices.addItemToCart(productCode, quantity, paymentMethod);
const {httpStatusCode, data, error, message} = response as APIResponse;
console.log('add_item_to_cart_response:', response);
switch (httpStatusCode) {
case httpStatusCodes.SUCCESS_OK:
case httpStatusCodes.SUCCESS_CREATED:
dispatch(cartActions.updateCartLoadingStatus(false));
dispatch(cartActions.updateCartItemCount(quantity));
globalConfig.setCartItemCount(quantity);
break;
case httpStatusCodes.CLIENT_ERROR_UNAUTHORIZED:
dispatch(cartActions.updateCartLoadingStatus(false));
break;
case httpStatusCodes.SERVER_ERROR_INTERNAL_SERVER_ERROR:
case httpStatusCodes.CLIENT_ERROR_BAD_REQUEST:
dispatch(cartActions.updateCartLoadingStatus(false));
Alert.alert("Alert", (message)? message : "Error, it looks like you already have access to this course.", [
{
text: "Ok",
},
]);
break;
default: {
dispatch(cartActions.updateCartLoadingStatus(false));
}
}
};
};
Reducers.ts
const initialState: CartInitialState = {
isLoading: true,
cartToken: "",
responseStatus: apiResponseStatuses.IDLE,
cartItemCount: 0,
isMessageVisible: false,
message: "",
};
export default function cartReducer(
state = initialState,
action: CartActionTypes,
): CartInitialState {
switch (action.type) {
case UPDATE_LOADING_STATUS:
return {
...state,
isLoading: action.isLoading,
};
case UPDATE_CART_TOKEN:
return {
...state,
cartToken: action.cartToken,
};
case UPDATE_RESPONSE_STATUS:
return {
...state,
responseStatus: action.responseStatus,
};
case UPDATE_CART_ITEM_COUNT_TOKEN:
return {
...state,
cartItemCount: action.cartItemCount,
};
case CLEAR_DATA_ON_LOGOUT:
return {
...state,
isLoading: true,
cartToken: "",
responseStatus: apiResponseStatuses.IDLE,
cartItemCount: 0,
isMessageVisible: false,
message: "",
};
default: {
return state;
}
}
}
In the pruchaseItem() function of CourseSpecificScreen.tsx, I would like to dispatch a action that adds the item to the cart and immediately afterwards check if the cartItemCount has been updated & if it has, do something... This functionality works as expected, but only after clicking the Buy Now button twice.
I have ruled out the possibility of the issue being the API request failing the first time.
I have been stuck on this issue for several days now so any help or advice would be greatly appreciated. Let me know if I need to include more information
In my case, I was storing a reference of the old cartReducer state before it was being updated.
I got this working by updating my purchaseItem() function to look like the below:
pruchaseItem = async () => {
const {
courseSpecificReducer,
clearCartAndAddItem,
navigation
} = this.props;
const paymentMethod = paymentMethodForDevice();
await clearCartAndAddItem(
paymentMethod,
courseSpecificReducer.productData.code as string,
1,
navigation
)
const { cartReducer } = this.props;
if(cartReducer.cartItemCount > 0) {
// do some stuff
}
};

Redux change a reducer of type object

So I have the following reducer
const objectType = (state = {type: 0, image:defaultImage, moreOptions: {tap: 0, griff: 0} },
action) => {....
case 'CHANG_OPTIONS':
return state = {...state, moreOptions: {tap:action.tap, griff: action.griff}}
This is the action, so I get a dynamic category and assign the id of the product.
export const changeOptions = (category, id) => {
return {
type: 'CHANG_OPTIONS',
[category]: id,
}
}
An example of dispatch would be
dispatch(changeOptions('tap', 0))
Now whenever I click on a tap or a griff, my object remove the other category from the list.
Here is a screenshot from the Redux Debugger tool
I'm sure that the problem is in my reducer:
moreOptions: {tap:action.tap, griff: action.griff} Is there a way I can spread the object and update only the one that was changed?
It's because you're overwritting both tap and griff value regardless of their input value. Try below.
const newOptions = {};
if (action.tap) {
newOptions.tap = action.tap;
}
if (action.griff) {
newOptions.griff = action.griff;
}
return (state = {
...state,
moreOptions: {
...state.moreOptions,
...newOptions
}
});

how to save array object data in redux store

i try to store multiple object in redux store on my react native app, but only one object is save,
i'm new at redux, i try a lot of solutions found on StackOverflow but no one works :/
result i have in my store:
"hives": {"hive_id": 12944, "hive_name": null}
result i want (or something like that) :
"hives": [
1: {"hive_id": 123, "hive_name": "HelloHive"},
2: {"hive_id": 12944, "hive_name": null}]
store:
const middleware = [thunk]
export const store = createStore(persistedReducer, applyMiddleware(...middleware));
export const persistor = persistStore(store);
reducer :
const INIT_STATE = {
hives: [],
}
const hiveReducer = (state = INIT_STATE, action) => {
switch (action.type) {
case SET_HIVES:
return {
...state,
hives: action.payload,
};
[...]
action creator:
export const setHives = hives => {
return {
type: SET_HIVES,
payload: hives,
};
};
action:
export const getHives = () => {
return dispatch => {
axios.get(GET_HIVE_URL, HEADER).then(res => {
const status = res.data.status;
const hives = res.data.hives;
if (status == 'hiveFound') {
for (let i = 0; i < hives.length; i++) {
console.log(hives[i]);
dispatch(setHives(hives[i]));
}
}
});
};
};
and my API send me:
"hives": [
{
"hive_id": 123,
"hive_name": "HelloHive"
},
{
"hive_id": 12944,
"hive_name": null
}
]
and console.log(hives[i]) return :
LOG {"hive_id": 123, "hive_name": "HelloHive"}
LOG {"hive_id": 12944, "hive_name": null}
thanks you
First of all, in your reducer you don't need to use ...state spread operator, since hives seems to be the only one variable in your state there. And second, you are iterating over each element of hives, therefore you are inputting them one by one thus overwriting the previous one. You are not appending it to array. Here's how you need to change your action:
export const getHives = () => {
return dispatch => {
axios.get(GET_HIVE_URL, HEADER).then(res => {
const status = res.data.status;
const hives = res.data.hives;
if (status == 'hiveFound') {
dispatch(setHives(hives));
}
});
};
};
This way it will write the whole array into that variable in redux.
You can try this below so you can store the whole array. assuming you already have the actions.
InitialState
export default {
hives:[]
}
HivesReducer
export default function counter(state = initialState.hives, action) {
switch (action.type) {
case Types.SET_HIVES:
return [...state, action.payload];
default:
return state;
}
}
In your reducer try this :
case SET_HIVES:
return {
...state,
hives: [...state.hives,action.payload],
};
[...]
hope it helps. feel free for doubts

React Native Flatlist extraData not working redux data changed

I have array in redux. I am showing datas on Flatlist. However, When I edited array data , flatlist not re-render. How can i solve this problem? I checked my redux and is working fine
this.props.notes[this.state.Index]={
color: JSON.stringify(BgColor),
date: this.state.fullDate.toString(),
note: this.state.noteText,
name: this.state.noteName,
type: "note",
noteID:this.props.notes[this.state.Index].noteID
}
this.props.editNotes(this.props.notes);
Flatlist code;
<FlatList
ref={(list) => this.myFlatList = list}
data={this.props.notes}
showsVerticalScrollIndicator={false}
renderItem={({item, index})=>(
)}
removeClippedSubviews={true}
extraData={this.props.notes}
/>
mapStateToProps on same page with Flatlist
const mapStateToProps = (state) => {
const { notes } = state
return { notes }
};
Reducer
const notes = [];
const notesReducer = (state = notes, action) => {
switch (action.type) {
case 'editNotes':
return state = action.payload;
default:
return state
}
};
export default notesReducer;
The reason it's not updating is because you're not returning a new array. The reference is same.
Return the updated state like return [...state,action.payload]
The reason it's not updating the data correctly is because the mutation.
The problematic code is this part.
this.props.notes[this.state.Index]={
color: JSON.stringify(BgColor),
date: this.state.fullDate.toString(),
note: this.state.noteText,
name: this.state.noteName,
type: "note",
noteID:this.props.notes[this.state.Index].noteID
}
this.props.editNotes(this.props.notes);
It should be in this way
const { notes, editNotes } = this.props;
const newNotes = [...notes];
const { index } = this.state;
newNotes[index] = {
//update data
}
editNotes(newNotes);
You can fix the issue in many ways but the wrong part I see in your code is Reducer. As per the standard, your reducer should be a Pure Function and the state should not mutate.
const notes = [];
const notesReducer = (state = notes, action) => {
switch (action.type) {
case 'editNotes':
return {
...state,
...action.payload;
},
default:
return state
}
};
export default notesReducer;
This should resolve your issue.
Suggestion:
Try to create a nested hierarchy in redux like
const initialState = {
notes: [],
};
const notesReducer = (state = initialState, action) => {
switch (action.type) {
case 'editNotes':
return {
...state,
notes: [
...state.notes,
...action.payload.notes,
],
},
default:
return state
}
};
export default notesReducer;

Redux store doesn't update all changes in app.js

such an honor to drop my first question in this community! I'm working on a recipe app where I use Redux to manage states. I'm using async storage to store changes locally. I'm a bit stuck now because my store only applies and stores a few changes instead of the whole recipe.
This is how the data of a recipe looks like (sorry for my Dutch):
{
cardId: 2,
time: "5 minutes",
title: "Wortel-Kokossoep met Dadelroom",
category: "ontbijt",
image: require("./assets/wortel-kokossoep.jpg"),
subtitle: "Gezonde en makkelijke soep!",
caption: "Wortel-Kokossoep met Dadelroom",
description:
"Begin de dag gezond met deze smoothie dat rijk is aan vitamines.",
stepOne: "Stap 1: Voeg alles toe aan de NutriBullet of blender.",
stepTwo:
"Stap 2: Blend twee keer gedurende ongeveer 5 tot 10 seconden en je bent klaar!",
stepThree: "",
stepFour: "",
stepFive: "",
stepSix: "",
stepSeven: "",
stepEight: "",
favorite: false
},
and this is how I implemented Redux in the app.js. Please forgive me for posting the whole code. I'm still a noob, eager to learn everything about Redux and react.
const reducer = (state = initialState, action) => {
switch (action.type) {
case "FAV_RECIPIE":
//const recipie = state.recipies.find(r => (r.cardId = action.id));
const recipieIndex = state.recipies.findIndex(
r => r.cardId === action.id
);
const currentValue = state.recipies[recipieIndex].favorite;
state.recipies[recipieIndex].favorite = !currentValue;
state.recipies = [...state.recipies];
saveRecipes(state.recipies); // save to local storage
return { ...state };
case "SET_LOADED_RECIPIES":
console.warn("!!!!");
if (action.recipies) {
state.recipies = [...JSON.parse(action.recipies)]; // JSON parse to convert string back to list
}
console.log("set recipies");
return { ...state };
case "OPEN_MENU":
return { action: "openMenu" };
case "CLOSE_MENU":
return { action: "closeMenu" };
default:
return state;
}
};
const saveRecipes = async recipies => {
try {
await AsyncStorage.setItem("#VV:Recipes", JSON.stringify(recipies)); // JSON stringify to convert list to string (for storage)
} catch (error) {
// error saving, and that is fine =)
console.log("could not save recipes");
}
};
const store = createStore(reducer, initialState);
store.subscribe(() => {
console.log("store changed", store.getState().recipies);
});
const App = () => (
<Provider store={store}>
<AppNavigator />
</Provider>
);
export default App;
I really hope some of you can help me out! Thanks in advance!
There's a couple of things going wrong in your reducer, but the big thing is doing state-mutations. You want to avoid logic like:
state.recipies[recipieIndex].favorite = !currentValue;
also
state.recipies = [...state.recipies];
This is against redux principles. You never want to directly change values of the state without first making a copy or clone.
So we will go with creating a shallow-copy of state in your reducer and make updates to that instead:
const reducer = (state = initialState, action) => {
switch (action.type) {
case "FAV_RECIPIE":
var newState = {...state}
//const recipie = state.recipies.find(r => (r.cardId = action.id));
const recipieIndex = state.recipies.findIndex(
r => r.cardId === action.id
);
const currentValue = state.recipies[recipieIndex].favorite;
newState.recipies[recipieIndex].favorite = !currentValue;
saveRecipes(newState.recipies); // save to local storage
return { ...newState };
case "SET_LOADED_RECIPIES":
console.warn("!!!!");
var newState = [...state]
if (action.recipies) {
newState.recipies = [...JSON.parse(action.recipies)]; // JSON parse to convert string back to list
}
console.log("set recipies");
return { ...newState };
case "OPEN_MENU":
return { action: "openMenu" };
case "CLOSE_MENU":
return { action: "closeMenu" };
default:
return state;
}
};
Alternatively we can handle this succinctly using .map() which creates a copy for us.
const reducer = (state = initialState, action) => {
switch (action.type) {
case "FAV_RECIPIE":
const updatedRecipes = {
...state,
recipes: state.recipes.map(recipe => {
if (recipe.cardId === action.id) {
return {
...recipe,
favorite: !recipe.favorite
};
} else {
return recipe;
}
})
};
saveRecipes(updatedRecipes)
return {
...updatedRecipes
}
case "SET_LOADED_RECIPIES":
var newState = {...state};
if (action.recipies) {
newState.recipies = [...JSON.parse(action.recipies)]; // JSON parse to convert string back to list
}
return { ...newState };
case "OPEN_MENU":
return { action: "openMenu" };
case "CLOSE_MENU":
return { action: "closeMenu" };
default:
return state;
}
};

Resources