I have to create a text area which taken multiple links then I split() into array yeah Its working fine, but I want to set that array into my state in linkList: [] but when I click to button for submitting it gives me empty array as I initialize. but when I again press to submit button then it gives me my desired list, why? here are code and outputs
onSubmit = event => {
this.setState({ loading: true, host: undefined });
const { text, linkList } = this.state;
console.log(text);
const mList = text.split("\n").filter(String);
console.log(mList);
this.setState({
linkList: [...mList]
});
console.log(linkList);
event.preventDefault();
};
Output console (First Click)
youtube.com
google.com
facebook.com
------------------------------------------------------------
["youtube.com", "google.com", "facebook.com"]
------------------------------------------------------------
[]
Output Console (Second Click)
youtube.com
google.com
facebook.com
---------------------------------------------
["youtube.com", "google.com", "facebook.com"]
---------------------------------------------
["youtube.com", "google.com", "facebook.com"]
setState is asynchronous. That means it doesn't happen right away, but a very short time later instead. If you add a:
console.log(linkList)
to the top of your render method, you will see the items being appended just as you expect.
It probably is being appended, it's just not available until the next render.
From the documentation:
setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall. Instead, use componentDidUpdate or a setState callback (setState(updater, callback)), either of which are guaranteed to fire after the update has been applied.
The below code might help.
onSubmit = event => {
this.setState({ loading: true, host: undefined }, () => {
const { text, linkList } = this.state;
console.log(link);
const mList = text.split("\n").filter(String);
console.log(mList);
this.setState({
linkList: [...mList]
}, () => {
console.log(linkList);
event.preventDefault();
});
});
};
Related
I am using mui-datatable and based on the official example of this codesandbox, you can setState on the tableState. https://codesandbox.io/s/trusting-jackson-k6t7ot?file=/examples/on-table-init/index.js
handleTableInit = (action, tableState) => {
console.log("handleTableInit: ", tableState);
this.setState({ table: tableState });
};
handleTableChange = (action, tableState) => {
console.log("handleTableChange: ", tableState);
this.setState({ table: tableState });
};
I wanted to get the the tableState.displayData hence, I added this, however, this will result to an error that says:
Maximum update depth exceeded. This can happen when a component
repeatedly calls setState inside componentWillUpdate or
componentDidUpdate. React limits the number of nested updates to
prevent infinite loops.
const handleChange = (action, tableState) => {
console.log(tableState.displayData);
setDisplayedData(tableState.displayData);
};
const options = {
enableNestedDataAccess: ".",
print: false,
filterType: "multiselect",
selectableRows: "none",
downloadOptions: { filename: "Data.csv", separator: "," },
expandableRows: true,
onTableChange: handleChange,
onTableInit: handleTableChange,
I wanted to store the data of the tableState.displayData to the setDisplayedData. How can I fix this error?
I recreated this error on codesandbox: https://codesandbox.io/s/mui-datatable-reports-mqrbb3?file=/src/App.js:4130-4415
This is keep rendering because you have used setDisplayedData in the handleChange function. so whenever table change you update the state and it again changing the state. So it is going to an infinite loop.
you should put condition to check if data you are getting is different from the prev one or not. you can try isEqualwith & isEqual functions from lodash library to check if you new data is different from old or not.
const handleChange = (action, tableState) => {
console.log(tableState.displayData);
if(!isEqualwith(displayedData, tableState.displayData, isEqual)) {
setDisplayedData([...tableState.displayData]);}
};
Note: add lodash to you dependencies and import isEqualwith & isEqual functions.
I have the following snippet of React that I can't get to work right. Essentially I'm displaying a spinner based on the state of this onChange call (due to the update sometimes taking 3-5 seconds). However, the first call to set state seems to go unnoticed (or as I'm researching being batched up) and therefore the state of loading is never updated. This is part of a toggle/switch a user can select infinite times on a page (if they desired obviously).
const [state, setState] = useState({ loading: false });
const onToggleChange = () => {
setState(prevState => {
return {
...prevState,
loading: true,
};
});
console.log("loading1: " + JSON.stringify(state));
// this takes a long time depending on the users actions
setFilterMode(!filterChecked);
setState(prevState => {
return {
...prevState,
loading: false,
};
});
console.log("loading2: " + JSON.stringify(state));
};
I've reviewed the current questions/answer (which is how I got as far as the above), but it still doesn't load correctly.
So I have this nuxt page /pages/:id.
In there, I do load the page content with:
content: function(){
return this.$store.state.pages.find(p => p.id === this.$route.params.id)
},
subcontent: function() {
return this.content.subcontent;
}
But I also have an action in this page to delete it. When the user clicks this button, I need to:
call the server and update the state with the result
redirect to the index: /pages
// 1
const serverCall = async () => {
const remainingPages = await mutateApi({
name: 'deletePage',
params: {id}
});
this.$store.dispatch('applications/updateState', remainingPages)
}
// 2
const redirect = () => {
this.$router.push({
path: '/pages'
});
}
Those two actions happen concurrently and I can't orchestrate those correctly:
I get an error TypeError: Cannot read property 'subcontent' of undefined, which means that the page properties are recalculated before the redirect actually happens.
I tried:
await server call then redirect
set a beforeUpdate() in the component hooks to handle redirect if this.content is empty.
delay of 0ms the server call and redirecting first
subcontent: function() {
if (!this.content.subcontent) return redirect();
return this.content.subcontent;
}
None of those worked. In all cases the current page components are recalculated first.
What worked is:
redirect();
setTimeout(() => {
serverCall();
}, 1000);
But it is obviously ugly.
Can anyone help on this?
As you hinted, using a timeout is not a good practice since you don't know how long it will take for the page to be destroyed, and thus you don't know which event will be executed first by the javascript event loop.
A good practice would be to dynamically register a 'destroyed' hook to your page, like so:
methods: {
deletePage() {
this.$once('hook:destroyed', serverCall)
redirect()
},
},
Note: you can also use the 'beforeDestroy' hook and it should work equally fine.
This is the sequence of events occurring:
serverCall() dispatches an update, modifying $store.state.pages.
content (which depends on $store.state.pages) recomputes, but $route.params.id is equal to the ID of the page just deleted, so Array.prototype.find() returns undefined.
subcontent (which depends on content) recomputes, and dereferences the undefined.
One solution is to check for the undefined before dereferencing:
export default {
computed: {
content() {...},
subcontent() {
return this.content?.subcontent
👆
// OR
return this.content && this.content.subcontent
}
}
}
demo
UPDATE: Yes for use case 1, if I extract search.value outside the useEffect and use it as a dependency it works.
But I have an updated Use case below
Use Case 2: I want to pass a searchHits Object to the server. The server in turn return it back to me with an updated value in response.
If I try using the searchHits Object I still get the infinite loop
state: {
visible: true,
loading: false,
search: {
value: “”,
searchHits: {....},
highlight: false,
}
}
let val = search.value
let hits = search.searchHits
useEffect( () => {
axios.post(`/search=${state.search.value}`, {hits: hits}).then( resp => {
…do something or ..do nothing
state.setState( prevState => {
return {
…prevState,
search: {... prevState.search, hits: resp.hit}
}
})
})
}, [val, hits])
Use Case 1: I want to search for a string and then highlight when I get results
e.g.
state: {
visible: true,
loading: false,
search: {
value: “”,
highlight: false,
}
}
useEffect( () => {
axios.get(`/search=${state.search.value}`).then( resp => {
…do something or ..do nothing
state.setState( prevState => {
return {
…prevState,
search: {... prevState.search, highlight: true}
}
})
})
}, [state.search])
In useEffect I make the API call using search.value.
eslint complains that there is a dependency on state.search , it does not recognize state.search.value. Even if you pass state.search.value it complains about state.search
Now if you pass state.search as dependecy it goes in an infinite loop because after the api call we are updating the highlights flag inside search.
Which will trigger another state update and a recursive loop.
One way to avoid this is to not have nested Objects in state or move the highlights flag outside search, but I am trying to not go that route give the sheer dependecies I have.
I would rather have an Object in state called search the way it is. Is there any way to better approach this.
If I want to keep my state Object as above how do I handle the infinite loop
Just a eslint stuff bug may be. You have retracted some code by saying //do something and have hidden he code. Are you sure that it doesn't have anything to do with search object?
Also, try to extract the variable out before useEffect().
const searchValue = state.search.value;
useEffect(()=>{// axios call here},[searchValue])
If your search value is an object, react does shallow comparison and it might not give desired result. Re-rendering on a set of object dependencies isn't ideal. Extract the variables.
React does shallow comparison of dependencies specified in useEffect
eg.,
const {searchParam1, searchParam2} = search.value;
useEffect(() => {
//logic goes here
}, [searchParam1, searchParam2]);
Additionally, you can add dev dependency for eslint-plugin-react-hooks, to identify common errors with hooks
I'm using a variable twice within a function but it returns different values even though I'm making no modifications to it.
This is happening within a form component developed with Vue.js (v2) which dispatches a Vuex action. I think this has nothing to do with Vue/Vuex per se, but it's important to understand part of the code.
Here is the relevant piece of code from my component
import { mapActions } from 'vuex'
export default {
data() {
return {
product: {
code: '',
description: '',
type: '',
productImage: [],
productDocs: {},
}
}
},
methods: {
...mapActions(['event']),
save() {
console.log("this.product:", this.product)
const valid = this.$refs.form.validate() // this validates the form
console.log("this.product:", this.product)
if (valid) {
try {
this.event({
action: 'product/addProduct',
data: this.product
})
}
finally {
this.close()
}
}
},
// other stuff
and a small piece of code for the vuex action "event"
event: async ({ dispatch }, event) => {
const time = new Date()
const evid = `${Date.now()}|${Math.floor(Math.random()*1000)}`
console.log(`Preparing to dispatch... Action: ${event.action} | data: ${JSON.stringify(event.data)} | Event ID: ${evid}`)
// enriching event
event.evid = evid;
event.timestamp = time;
event.synced = 0
// Push user event to buffer
try {
await buffer.events.add(event)
} catch (e) {
console.log(`Error writing event into buffer. Action ${event.action} | evid: ${evid} `)
}
// dispatch action
try {
await dispatch(event.action, event)
}
catch (err) {
console.log(`Error dispatching action: ${event.action} | data: ${event.data}\n${err.stack || err}`)
window.alert('Could not save. Try again. \n' + err + `\n Action: ${event.action} | data: ${event.data}`)
}
},
The problem is with this.product. I've placed the several console.log to check out the actual values because it wasn't working as expected. The logs from the save() functions return undefined, but within the event function (a vuex action) the values are as expected, as shown in the console logs:
When I log this.product in the save() function. Both logs are the same.
When I log the event in the vuex action, it shows that event.data is actually the product.
I must be doing something terribly wrong here, but I'm totally blind to it. Any help is appreciated.
#Sumurai8: thanks for editing the question and for the hint.
Part of this may be because of that tiny i next to the opened product.
If you hover over it, it says that "the object has been evaluated just
now", which means it evaluates what is in the object when you open the
object, which is way after executing the action. [...] Whatever is
changing the product may very well happen after the event somewhere.
It actually helped me find the solution.
Basically within the this.close function called in the finally statement of the save() function, I was resetting the form and thus this.product, which was used solely to hold the form data. So at evaluation time, the object had undefined properties, while the event function managed to output to the console before the reset. However at the end the store would not get updated as expected (that's how I noticed the issue), because the event function and the action called within it are asynchronous and so the value got reset before the actual mutation of the vuex store.
Logging JSON.stringify(this.product) outputted the right value even within the save() method. I used that to create a more robust copy of the data and passed that to the event function as follows:
this.event({
action: 'product/addProduct',
data: JSON.parse(JSON.stringify(this.product))
})
Now everything works like a charme.