React JS Updating item in State object to take effect immediately - javascript

React JS class component
I know there have been many posts on this subject but I can't seem to get this scenario to work.
Basically on my HandleClickSave event I want to update an item in my object in state without affecting the other values and then passing this updated oblect onto my service to get updated in the db.
The 'item' in question is the 'design' from the (unLayer) React-email-editor.
Problem is after the service is run in 'HandleClickSave' point 3 below, the receiving field 'DesignStructure' in the db is NULL every time. The other two fields are fine as these are saved to state object elsewhere.
Part of the problem is that the Email-Editor doesn't have an 'onChange' property which is where I would normally update the state. The other two values in the object are input texts and they do have an onChange which is how their state counterparts are updated.
This is the object 'NewsletterDesign':
{
"DesignId": "1",
"DesignName": "DesignLayout 1 Test",
"DesignStructure": null
}
In my React class component...
this.state = {
NewsletterDesign: {}
}
And the HandleClickSave event....
HandleClickSave () {
const { NewsletterDesign } = this.state
this.editor.saveDesign((design) => {
this.setState(prevState => ({
NewsletterDesign: {
...prevState.NewsletterDesign,
DesignStructure: design
}
}));
// Update to database passing in the object 'NewsletterDesign'. Field 'DesignStructure' in db is null every time, but other two fields are updated.
NewsletterService.UpdateCreateNewsletterDesign(NewsletterDesign)
etc....etc..

React's setState is not update immediately. read more here.
You can simply do it inside setState by
this.setState(prevState => {
const newState = {
NewsletterDesign: {
...prevState.NewsletterDesign,
DesignStructure: design
}
};
NewsletterService.UpdateCreateNewsletterDesign(newState.NewsletterDesign);
return newState;
});

The setState is an async operation. Meaning, that it's not guaranteed that the new state that you have updated will be accessible just after the state is updated. You can read more here
So in such cases, one of the way is to do the required operation first and then use the result at multiple places.
HandleClickSave () {
const { NewsletterDesign } = this.state
this.editor.saveDesign((design) => {
let newNewsletterDesign = { ...NewsletterDesign,
DesignStructure: design
};
this.setState(newNewsletterDesign);
NewsletterService.UpdateCreateNewsletterDesign(newNewsletterDesign)

Related

My mapStateToProps is not called after adding custom object array to redux state

I am trying for few hours but can't figure out why my state is not called after adding an array of custom object.
// In my component...
const myRemoteArray = getRemoteArray() // Is working
props.addAdItems(myRemoteArray) // Calls **1 via component.props
/// ...
const mapDispatchToProps = (dispatch) => {
return {
addAdItems: (items) => { // **1
// Items contains my array of objects
dispatch(addAdItems(items)) // Calls **2
},
}
}
// My action
export const addAdItems = (items) => { // **2
// Items contains my array of objects
return { // Calls **3
type: AD_ITEMS,
adItems: items,
}
}
const productsReducer = (state = initialState, action) => {
switch (action.type) { // **3
case AD_ITEMS:
// Is working!
// action.adItems contains my array!
const _state = {
...state,
adItems: action.adItems, // Here is the issue, I am not sure how to add my NEW array to existing state and update it.
// Like that: ??? "adItems: ...action.adItems" or adItems: [action.adItems]
}
// The new state contains my Array!!!
return _state
default:
return state
}
}
// In my component... !!!!
// THIS IS NOT CALLED or it is called with empty array from initialState!!!
const mapStateToProps = (state) => {
return {
updatedItem: state.changedItem,
adItems: state.adItems,
}
}
It seems to me that Redux is having a problem with my array containing the following data. Has Redux issues with my class methods?
class Ad {
constructor(
id,
isPublished
) {
this.id = id
this.isPublished = isPublished
}
someMessage = () => { return "Help me!" }
needHelp = () => { return true }
}
My Redux is working already with other calls, data, and objects, which means my createStore and all other stuff is correct.
PS: I don't have multiple stores.
UPDATE
Now my mapDispatchToProps is called with current array but is not persisting.
UPDATE 2
If I save my file and force to refresh the App, the props.adItems contains my loaded array, but if I want to access props.adItems at runtime (e.g. on FlatList refresh) it is empty array again!
Why?
Should I store my array in a useState property after it has changes via useEffect?
You were pretty close in the comments you added in the reducer, but neither of them were 100% accurate.
For Redux to notice that your array has changed, you need the property adItems of your new state to return an entirely new array. You can do it like this:
adItems: [...action.adItems]
With this code you'll be creating a new array, and then adding a copy of the items of the old one into it.
The reason why your current implementation (adItems: action.adItems) is not working is that action.adItems is actually a reference to an array in memory. Even though the array contents have changed, the value of action.adItems is still the same, a pointer to where the array is currently stored. This is the reason why your store is not being updated: as Redux does not check the values of the array itself but the reference to where the array is stored, the new state you're returning is exactly the same, so Redux is not aware of any changes.
As LonelyPrincess says, I was making this issue elsewhere, if you doing that xArray = yArra it means call by reference and not by value.

Is it ok to modify Vuex state using only the payload argument of a mutation?

For example, could I iterate over Vuex data in a Vue file and choose the data needing updating, then pass the found data to an action, which commits it and then the mutation only makes the update?
The reason I'm unsure about it is because the typical format of a Vuex mutation contains the parameter for 'state', so I assume it needs to be used, and the only way to do that is either by doing all the looping inside the mutation, or to pass indexes to it to more quickly find the exact fields needing changing.
For who asked, a code example:
someVueFile.vue
computed: {
...mapState({
arrayOfObjects: (state) => state.someVuexStore.arrayOfObjects
}),
},
methods: {
myUpdateMethod() {
let toBePassedForUpdate = null;
let newFieldState = "oneValue";
this.arrayOfObjects.forEach((myObject) => {
if (myObject.someDataField !== "oneValue") {
toBePassedForUpdate = myObject.someDataField;
}
})
if (toBePassedForUpdate) {
let passObject = {
updateThis: toBePassedForUpdate,
newFieldState: newFieldState
}
this.$store.dispatch("updateMyObjectField", passObject)
}
}
}
someVuexStore.js
const state = {
arrayOfObjects: [],
/* contains some object such as:
myCoolObject: {
someDataField: "otherValue"
}
*/
}
const mutations = {
updateMyObjectField(state, data) {
data.updateThis = data.newFieldState;
}
}
const actions = {
updateMyObjectField(state, data) {
state.commit("updateMyObjectField", data);
}
}
Yes, it's alright to mutate state passed in through the payload argument rather than state. Vuex doesn't bother to distinguish between the two. In either case, it's the same state, and neither option detracts from the purposes of using mutations.
To feel more sure of that, you can ask what are the purposes of mutations and of enforcing their use. The answer is to keep a centralized, trackable location for concretely defined changes to state.
To illustrate this is a good thing, imagine an app with 1000 components, each one changing state locally, outside of a mutation, and in different ways. This could be a nightmare to debug or comprehend as a 3rd party, because you don't know how or where state changes.
So mutations enforce how and a centralized where. Neither of these are damaged by only using the payload argument in a mutation.
I would do all of the logic from one action, you can desctructured the context object in the action signature like so :
actions: {
myAction ({ state, commit, getters, dispacth } ,anyOtherParameter) {
let myVar = getters.myGetter//use a getter to get your data
//execute logic
commit('myCommit', myVar)//commit the change
}
}
If you need to do the logic in your component you can easily extract the getter and the logic from the action.

Multiple set state within multiple api call inside componentDidMount in ReactJS

I am new in React and trying to call multiple api call within componentDidMount function.
My code is
componentDidMount() {
Promise.all([
axios.get(<url1>),
axios.get(<url2>)
]).then(([res1, res2]) => {
// call setState here
const users = res1.data.users;
this.setState({ users: users});
const banks = res2.data.banks;
this.setState({ banks: banks});
console.log("Users")
console.log(users) // It works
console.log("Banks")
console.log(banks) // It works
})
}
render() {
console.log(this.state.users.length) // Gives length
console.log(this.state.banks.length) // Gives undefined
return (
<div className='popup'></div>
)
}
The problem is inside render function the second state banks length is undefined.
How can I do multiple setstate inside componentDidMount.
Any help is highly appreciated.
Update: Resolved
The mistake was
constructor(props) {
super(props);
this.state = {
users: [],
//MISSING BANKS array
}
}
You should set state in a single update, updating both values at the same time. Otherwise you are instructing React to initiate two renders, the first would contain users value and an undefined value for banks (depending on your initial state declaration). This render would be quickly followed by a second pass, in which both state values users and banks would be defined.
The below example should work as required, in a single render.
Promise.all([
axios.get(<url1>),
axios.get(<url2>)
]).then(([res1, res2]) => {
// call setState here
const users = res1.data.users;
const banks = res2.data.banks;
this.setState({ users, banks });
});
On the other hand, if for some strange requirement you actually want two sequential renders you can use setState's done callback; example below.
this.setState({ users }, () => {
this.setState({ banks });
});
This will ensure the first render is complete before requesting a new render via setState.

want to show updated status value in another component

i want to watch when a mutation called and updated a status. i make a component to show database table count when api called.
this is my store i wrote
const state = {
opportunity: ""
}
const getters = {
countOpportunity: state => state.opportunity
}
const actions = {
// count opportunity
async totalOpportunity({ commit }) {
const response = await axios.get(count_opportunity)
commit("setOpportunity", response.data)
},
}
const mutations = {
setOpportunity: (state, value) => (state.opportunity = value)
}
i want to show this getter value when this mutation called in another component name Opportunity.vue file.
i showed database count values in file name Dashboard.vue
i wrote it like this.
computed: {
...mapGetters(["countOpportunity"])
},
watch: {},
mounted() {
//do something after mounting vue instance
this.$store.watch(() => {
this.$store.getters.countOpportunity;
});
},
created() {
this.totalOpportunity();
},
methods: {
...mapActions(["totalOpportunity"])
}
and showed my view like this.
<div class="inner">
<h3>{{ countOpportunity }}</h3>
<p>Opportunities</p>
</div>
when api called and count increase shows my mutations. but my view value not updated (countOpportunity). any one can help me to fix this.
The issue here (most likely) is that the value of response.data is an object or an array. You've initially defined opportunity as '' which is not an observable object or array. You have 2 choices:
Redefine it as an empty object or array, depending on the response:
opportunity: [] // or {}
Otherwise, use Vue.set() to apply reactivity when changing it:
(Vue.set(state, 'opportunity', value))

Vuex: Observe logic in state

I don't know how to organize my Vuex store given the following problem.
I have an array of buttons / actions, like 100s of them. They are are organized in the store like this:
buttons: [
{
text: 'Button 1',
doAction (store) {},
mustShow (store) {
return state.variable > 10 && state.variable2.counter < 12 && !state.variable3
}
}
...
]
I can easily display them in my view and link their action to the click event:
<button v-for"button in buttons" #click="button.doAction()"></button>
The problem is that each button can be shown or not based on arbitrary complex logic that it only knows, as you can see in the mustShow function. Each button has its distinctive logic.
I can easily make a getter that returns only the buttons whose mustShow function returns true to have only the actions that must be shown in a specific state of the store:
availableActions (state) {
return state.buttons.filter(s => s.mustShow())
}
This works the first time, but the problem is that of course this getter is not reactive since it's not bound to state variables but to the result of a function that is not reactive.
How would you organize the code to make this work? Of course one could put all the display logic for all the buttons into a single getter. But what if I want the name of the button to be dynamic as well (as the result of a function that computes its value based on arbitrary variables in the state)?
Thanks
I think you are going the wrong way here: as a thumb rule you shouldn't have complex objects, like function definitions, defining your store state. A way of thinking about the store state is that should be something that you should be able to encode in JSON, give it to a friend, and then your friend if parses it back and use it in the same program should get the same result, so clearly a function inside the state won't fit this.
My suggestion would be to do something like:
const state = {
buttons: [
{
text: 'Button 1',
id: 1
},
...
]
}
...
const actions = {
doAction ({commit}, {btnId}) {
// now you perform the action you want to do
...
// finally if you want to change the state of your store you
// should commit a mutation, *do not change the state here!*
// let the mutation do their job
// here you put all the things the mutation may need to perform
// the change of the state
const payload = { btnId }
commit(changeSomethingInState, { payload })
}
}
const mutations = {
changeSomethingInState (state, { payload }) {
state.something = payload
}
...
This is in the store definition. Now in your view you do like:
<button v-for"button in buttons" #click="dispatch('doAction', { btnId: button.id })"/>

Categories

Resources