React Native: Strange Issue With Updating State - javascript

I am using the code below:
makeRemoteRequest = () => {
let items = [];
models.forEach(element => { //models is an array of the list of models
this.pushReports(element, items);
});
console.log("This is the item array: ",items);
this.setState({
data:items
});
console.log("This is the data in state: ",this.state.data);
}
Somehow, the console log for the items array is showing me the array that I need, but the console log for the this.state.data is empty. How can this be possible? The log for the items array is run right before state is set.
This is preventing me from updating my state.

this.setState is rendering asynchronously. And you're trying to print in next line so it will not give immediate results as you want.
Solution: do this in next line,
setTimeout(() => {console.log("This is the data in state: ",this.state.data) }, 1000)

this.setState() does not run synchronously. Your state is not guaranteed to be updated on the next immediate line, but it will be updated properly on the next render cycle. Try putting console.log within render() and you'll see.
Discussion about this topic here: https://github.com/facebook/react/issues/11527#issuecomment-360199710

Since setState works in an asynchronous way. That means after calling setState the this.state is not immediately changed. So if you want to perform an action immediately after setting state, use 2nd argument as callback on setState. Consider this example:
this.setState({
data: newData
}, () => {
//TODO: Do something with this.state here
});

Related

Javascript object vs array ask Axios.get [duplicate]

Is there a reason that calling setSate() in a loop would prevent it from updating the state multiple times?
I have a very basic jsbin that highlights the problem I am seeing. There are two buttons. One updates the state's counter by 1. The other calls the underlying function of One in a loop -- which seemingly would update the state multiple times.
I know of several solutions to this problem but I want to make sure that I am understanding the underlying mechanism here first. Why can't setState be called in a loop? Do I have it coded awkwardly that is preventing the desired effect?
From the React Docs:
setState() enqueues changes to the component state and tells React that this component and its children need to be re-rendered with the updated state. This is the primary method you use to update the user interface in response to event handlers and server responses.
Think of setState() as a request rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately.
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.
Basically, don't call setState in a loop. What's happening here is exactly what the docs are referring to: this.state is returning the previous value, as the pending state update has not been applied yet.
There's a nice way to update state in a loop. Just make an empty variable, set its value to the updated state, call setState(), and pass it this variable:
const updatedState = {};
if (vars.length) {
vars.forEach(v => {
updatedState[v] = '';
this.setState({
...this.state
...updatedState,
});
});
}
You have to use something like that:
const MyComponent = () => {
const [myState, setMyState] = useState([]);
const handleSomething = (values) => {
values.map((value) => {
setMyState((oldValue) => [...oldValue, { key: value.dataWhatYouWant }]);
}
}
return (<> Content... </>);
}
I had the same problem. But tried with a little different approach.
iterateData(data){
//data to render
let copy=[];
for(let i=0;<data.length;i++){
copy.push(<SomeComp data=[i] />)
}
this.setState({
setComp:copy
});
}
render(){
return(
<div>
{this.state.setComp}
</div>
);
}
I hope this helps.
Basically setState is called asynchronously. It also has a callback function which you can utilise to do something once the state has been mutated.
Also if multiple setStates are called one after the other they are batched together as written previously.
Actually setState() method is asynchronous. Instead you can achieve it like this
manyClicks() {
var i = 0;
for (i = 0; i < 100; i++) {
//this.setState({clicks: this.state.clicks + 1}); instead of this
this.setState((prevState,props)=>({
clicks: ++prevState.clicks
}))
}
}
I was having this issue when creating a feature to import items.
Since the amount of the importing items could be huge, I need to provide feedback (like a progress bar) to the site user so that they know that they aren't sitting there and waiting for nothing.
As we know that we can't setState in a loop, I took a different approach by running the task recursively.
Here's a example code
https://codesandbox.io/s/react-playground-forked-5rssb
You can try this one using the previous value to increase the count.
function handleChange() {
for (let i = 0; i < 5; i++) {
setState(prev => {
return prev + 1
})
}
}
I was able to make your code work, calling setState in the loop by doing the following:
manyClicks() {
for (i = 0; i < 100; i++) {
this.setState({clicks: this.state.clicks += 1})
}
}
enter code here
Hopefully this helps!

Can not update the list of items?

My list of items is not being updated when I do a delete of a row even though the console.log(data) results in one less item after deleting the row, but the console.log('list of items after: ', listOfitems) gives me all full items even those that are deleted.
const [listOfitems, setListOfitems] = useState([]);
useEffect(() => {
getListOfitems();
}, [])
Please let me know what could be possibly wrong here:
editable={{
onRowDelete: (oldData) =>
new Promise((resolve, reject) => {
setTimeout(() => {
{
console.log('list of items before: ', listOfitems)
console.log('oldData.item_key;: ', oldData.item_key)
const indexToDelete = oldData.item_key;
console.log(indexToDelete)
const data =
listOfitems.filter(
(item) => ((item.item_key !== oldData.item_key))
)
setListOfitems(data);
console.log(data)
console.log(setListOfitems(data))
deleteitem(oldData);
setListOfitems(data);
console.log('list of items after: ', listOfitems)
}
resolve();
}, 1000);
}),
}}
The second argument in the useEffect hook is an array of values that the hook depends on. In your example you passed an empty array, which is great for making the hook work like ComponentDidMount(). However, since your hook now depends on no values, it will never fire again. In the below example, passing listOfItems into that array will cause the hook to fire whenever that piece of state changes. Not seeing the rest of your code, but based on your description of the problem, this is my best guess as to a solution for you.
Hope this helps.
const [listOfitems, setListOfitems] = useState([]);
useEffect(() => {
console.log('useEffect')
getListOfitems().then((listOfitems) => {
setListOfitems(listOfitems);
});
getDocuments();
}, [listOfItems]);
Every time the component is rendered a new const state is assigned to useState hook. In you case every time your component is rerendred this line runs assigning a new value to the listOfitems.
const [listOfitems, setListOfitems] = useState([]);
This means that console logging out the state before the component rerenders would return the old state.
Console.log(data) results in the correct state because you simply filter and return a new array.
As dvfleet413 mentioned you do not include the listOfItems in the argument array of the useEffect meaning that your component only renders once.
The set* methods for state are asynchronous, so if you try to access listOfitems immediately after calling setListOfitems, you could be using the old value and not the newly updated one.
You could use a separate useEffect method that is dependent on the value being set in the first one (use the second param like [listOfitems]). This other method should get triggered after the update.
In short - the data IS being updated, but your console.log is displaying the old value before the update is complete.
edit: Code example:
useEffect(()=>{
getListOfitems()
// console.log here may NOT show the new listOfitems value because
// setListOfitems is asynchronous, and getListOfitems may also be
}, []) // only loads on the initial mount
I assume getListOfitems will retrieve the data from an external source initially, and calls setListOfitems inside it. The code in the post does not show what is happening.
A second useEffect could look like:
useEffect(()=>{
// do something with listOfitems
}, [listOfitems]) // gets called every time listOfitems is updated

ReactJs : Setstate is not updating the state

Below is the initial state which is assigned
state = {
movie : null,
actors : null,
directors : [],
loading : false
}
Below is the set state which i set and at the end to set state as put in comment when it is set and not set. I'm not able to access the this.state outside.
this.setState({movie:result},() => {
//then fetch the actors
const endpoint= `${API_URL}movie/${this.props.match.params.movieId}/credits?api_key=${API_KEY}`;
fetch(endpoint)
.then(result => result.json())
.then(result=>{
const directors =result.crew.filter((member)=> member.job==='Director' );
this.setState({
actors : result.cast,
directors,
loading : false
})
console.log(this.state); //here value is coming
})
})console.log(this.state); //here value is not coming
How to get this.state value with updated info?
render() {
//removed code below mull div
return (
<div className="rmdb-movie">
{this.state.movie ?
<div>
<Navigation movie={this.props.location.movieName}/>
</div>
: null
}
</div>
)
}
What you're experiencing is the result of using asynchronous functions. The callback you pass to this.setState() is called whenever the state is set, and there might be a delay between when you call this.setState() and when the state is actually updated. This means that the callback may or may not be executed when you reach the second console.log().
This explains why you are seeing these results. The first console.log() is actually called after the second console.log.
This is react lifecycle ! The setState function is not executed instantaneously. The inner console.log prints the good values because in the callback of setState you are sur it is executed. The outer console.log is executed before setState has actually modified the state and so you don't see the updated info.
What you are seeing is actually what is expected, this is because Javascript in general and your specific fetch API call is asynchronous. Your first console.log() is inside the response from the fetch request, so it is only executed when the server returns the response(which could be a few seconds or more), then the state is set, then you print using console.log. The second console.log is outside, and will actually print instantly ,even before the async API call is made, before the state has actually been updated. Therefore, I would recommend that whatever action you want to do after the state is set, should be done where the first console.log is. Or preferably like this in a callback that you can pass to this.setState to ensure the state has been updated.
this.setState({movie:result},() => {
//then fetch the actors
const endpoint= `${API_URL}movie/${this.props.match.params.movieId}/credits?api_key=${API_KEY}`;
fetch(endpoint)
.then(result => result.json())
.then(result=>{
const directors =result.crew.filter((member)=> member.job==='Director' );
this.setState({
actors : result.cast,
directors,
loading : false
},()=>{
// API Call has returned and state has been updated
// Now access this.state
});
})
})
setState operation is asynchronous, you can read more about it in react documentation.
What problem are you trying to solve? Probably you need to use hook componentDidUpdate or just use this value in next render call.
I think you should rethink the code because there is no way result will get to movies the way you put it. Except somehow you update another state inside the API function to fetch movies. So somehow you can call fetch which you must have binded to your class
this.fetch = this.fetch.bind(this) from some onclick or onchange or something.
fetch =() =>{
const endpoint= `${API_URL}movie/${this.props.match.params.movieId}/credits?api_key=${API_KEY}`;
fetch(endpoint)
.then(result => result.json())
.then(result=>{
const directors =result.crew.filter((member)=> member.job==='Director' );
this.setState({
actors : result.cast,
directors,
loading : false,
movies:result
})
})
}

React: scrollIntoView only works inside of setTimeout

My application consists of a basic input where the user types a message. The message is then appended to the bottom of all of the other messages, much like a chat. When I add a new chat message to the array of messages I also want to scroll down to that message.
Each html element has a dynamically created ref based on its index in the loop which prints them out. The code that adds a new message attempts to scroll to the latest message after it has been added.
This code only works if it is placed within a setTimeout function. I cannot understand why this should be.
Code which creates the comments from their array
comments = this.state.item.notes.map((comment, i) => (
<div key={i} ref={i}>
<div className="comment-text">{comment.text}</div>
</div>
));
Button which adds a new comment
<input type="text" value={this.state.textInput} onChange={this.commentChange} />
<div className="submit-button" onClick={() => this.addComment()}>Submit</div>
Add Comment function
addComment = () => {
const value = this.state.textInput;
const comment = {
text: value,
ts: new Date(),
user: 'Test User',
};
const newComments = [...this.state.item.notes, comment];
const newState = { ...this.state.item, notes: newComments };
this.setState({ item: newState });
this.setState({ textInput: '' });
setTimeout(() => {
this.scrollToLatest();
}, 100);
}
scrollToLatest = () => {
const commentIndex = this.state.xrayModalData.notes.length - 1;
this.refs[commentIndex].scrollIntoView({ block: 'end', behavior: 'smooth' });
};
If I do not put the call to scrollToLatest() inside of a setTimeout, it does not work. It doesn't generate errors, it simply does nothing. My thought was that it was trying to run before the state was set fully, but I've tried adding a callback to the setState function to run it, and it also does not work.
Adding a new comment and ref will require another render in the component update lifecycle, and you're attempting to access the ref before it has been rendered (which the setTimeout resolved, kind of). You should endeavor to use the React component lifecycle methods. Try calling your scrollToLatest inside the lifecycle method componentDidUpdate, which is called after the render has been executed.
And while you're certainly correct that setting state is an asynchronous process, the updating lifecycle methods (for example, shouldComponentUpdate, render, and componentDidUpdate) are not initiated until after a state update, and your setState callback may be called before the component is actually updated by render. The React docs can provide some additional clarification on the component lifecycles.
Finally, so that your scroll method is not called on every update (just on the updates that matter), you can implement another lifecycle method, getSnapshotBeforeUpdate, which allows you to compare your previous state and current state, and pass a return value to componentDidUpdate.
getSnapshotBeforeUpdate(prevProps, prevState) {
// If relevant portion or prevState and state not equal return a
// value, else return null
}
componentDidUpdate(prevProps, prevState, snapshot) {
// Check value of snapshot. If null, do not call your scroll
// function
}

React's setState doesn't change state and update component

I'm calling console.log as a callback from setState and it seems like setState doesn't work. I'm trying to write a function which takes state defined like this:
this.state = {
...this.props.substate
};
this.props.substate is an array of objects, so I'm transforming it to object.
Here is a function that transforms state back to array, removes last element and then transforms array into object:
handleClickRem = event => {
event.preventDefault();
console.log(this.state);
const arrayFromState = Object.values(this.state);
console.log(arrayFromState);
const newArray = arrayFromState.slice(0, -1);
console.log('newArray',newArray);
const newState = {...newArray};
console.log(newState);
//newState is exactly what I want it to be
this.setState(newState, () => console.log(this.state));
};
All console logs works perfectly apart from the last one. I'm also checking state in render(), but it also seems to show no change of state.
I've done similarly a function, which adds elements to state and it works perfectly.
I'll be very thankful for help and patience :)

Categories

Resources