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

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;
}
};

Related

react redux thunk not populating state object

im having an issue with my code, its not populating the state object when state action is being performed. im new with redux
i have this code. so far that having an issue
this is the statement that will called the props.action fetchProjectFamilyList
case 'SubBusinessUnit':
setProductFamilyDetailsObj([])
if (selectedOption.id != 0) {
props.actions.fetchDepartment(selectedOption.id)
props.actions.fetchProjectFamilyList(selectedOption.id)
console.log(props)
}
setDropdownDataInState(resetData, 'Department')
setFormFields({
...formFields,
'OtherNamedInsuredIndustry': {
...formFields.OtherNamedInsuredIndustry,
value: ''
},
'NamedInsuredIndustry': {
...formFields.NamedInsuredIndustry,
value: "",
selectedId: 0
},
[fieldName]: {
...formFields[fieldName],
value: selectedOption.description, selectedId: selectedOption.id
}
});
break;
and this is the code for the commonreducer
export const fetchProjectFamilyList = createAsyncThunk(types.FETCH_PROJECT_FAMILY_LIST,
async (option, {getState, rejectWithValue}) => {
const reduxThunkConfig = {
checkStateData:getState().commonReducer.projectFamilyList && getState().commonReducer.projectFamilyList[option],
rejectWithValue
}
const APIConfig = {
URL: "eapi-referencedata/v1/lists/12?filterBySourceList=" + option + "&filterBySourceListValue=15",
method:"getData",
}
console.log('fetchProjectFamilyList')
return fetchCachedData(reduxThunkConfig, APIConfig);
}
)
im using the builder in my case of course inistailstate is set
const initialState = {
projectFamilyList:{},
}
builder.addCase(fetchProjectFamilyList.fulfilled, (state, action) => {
const subDivision = action.meta.arg;
return {
...state,
projectFamilyList:{
...state.projectFamilyList,
[subDivision]: action.payload},
}})
const commonActions = { ...actions, fetchProjectFamilyList }
export { commonActions, commonReducer}
this is the comment that accept the state as props. but the props productFamilyDetailsObj is empty object
<ProductFamilyComponent
productFamilyDetailsObj={productFamilyDetailsObj}
/>
function ProductFamilyComponent({ productFamilyDetailsObj }) {
return <div className="boxLayout">
<p className="smallHeading">Product Families</p>
{productFamilyDetailsObj.map((text, textIndex) => {
let index = textIndex;
return ( .... and so on
I hope theres someone who could help me resolving this. thank in advance.

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
}
};

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;

Adding item 2 levels deep in Redux Reducer

I am trying to add notes to a task object but what I have so far adds it to all the tasks. When I try different ways, it doesn't compile. The Object.assign doesn't like coming after the .push()
When it adds to all task:
let taskReducer = function(tasks = [], action) {
switch (action.type) {
case 'ADD_NOTE':
return tasks.map((task) => {
const { notes } = task;
const { text } = action;
notes.push({
text,
id: notes.length,
})
return task.id === action.id ?
Object.assign({}, { task, notes }) : task
})
When it doesn't compile:
let taskReducer = function(tasks = [], action) {
switch (action.type) {
case 'ADD_NOTE':
return tasks.map((task) => {
return task.id === action.id ?
const { notes } = task;
const { text } = action;
notes.push({
text,
id: notes.length,
})
Object.assign({}, { task, notes }) : task
})
You almost never want to use Array.push() in a reducer, because that directly mutates the existing array, and direct mutations generally break UI updates (see the Redux FAQ). You could use push() on a new copy of the old array, but most examples don't use that approach. Most of the time, the suggested approach is to use const newArray = oldArray.concat(newValue), which returns a new array reference containing all the old items plus the new item.
Beyond that, keep in mind that when updating nested data immutably, every level of nesting needs to have a copy made and returned.
Haven't actually tested this, but I think your code needs to look roughly like this example:
let taskReducer = function(tasks = [], action) {
switch (action.type) {
case 'ADD_NOTE':
return tasks.map((task) => {
if(action.id !== task.id) {
return task;
}
const { notes } = task;
const { text } = action;
const newNotes = notes.concat({id : notes.length, text});
const newTask = Object.assign({}, task, {notes : newNotes});
return newTask;
}
default : return tasks;
}
}

Resources