ReactJS state array pushing issues - reactjs

I have an array in a react state hook. I'm pushing values to it in a way that i assumed was the correct way, yet the array is always empty when i try to log it. What about my method of pushing to the array is not correct?
const [betHistory, setBetHistory] = useState([""]);
Inside my function (which i've tested is being called, and betCount is never blank)
function placeBet() {
console.log(betCount);
setBetHistory(betHistory => [...betHistory, betCount.toString()]);
console.log(betHistory);
}
The log is always blank.

Setting the state will update the value on the next render, not immediately. If you need to access the updated value right away, store it in a variable:
function placeBet() {
const newBetHistory = [...betHistory, betCount.toString()];
setBetHistory(newBetHistory);
console.log(newBetHistory);
}

Related

Why does setState([...arr]) cause a rerender but setState(arr) does not?

Shouldn't these do the same thing? The first code snippet causes my component to rerender
const context = React.useContext(myContext);
const { arr, setArr } = context;
const addItem = (item) => {
arr[0].subArr.push(item);
setArr([...arr]);
}
but this does not
const context = React.useContext(myContext);
const { arr, setArr } = context;
const addItem = (item) => {
arr[0].subArr.push(item);
setArr(arr);
}
setArr is just pulled from React.useState() there's no custom functionality I built
React will only re-render a component if at least item in its state (or context) changes. If all previous state values are === to the new state, React will not call the functional component at all and simply keep using what was rendered the last time.
This is one of the reasons you should never mutate state in React - it can lead to unexpected or unpredictable behavior.
In your case, when you do setArr(arr), since the arr is the exact same array that's currently in state context, React skips re-rendering because the new state is === to the old state. Only by creating a new array which is not === to what is currently in state does a re-render occur.
To understand this you have understood how arrays are being treated in JS. In plain English, when you create an array JS knows the array reference, which is the memory address where the array is being stored. Now although you are changing the items in the array it doesn't change the reference. When JS tries to find that array it finds it in the old location. Therefore, from JS perspective the array is not changed although the items are changed. And we know that react doesn't re-render unless there is a change in the state. So, when you write setState(arr), react finds that the array reference is not changed therefore no re-render.
On the other hand, when you write setState([...arr]), we are creating a new array with the items of the arr array. So, in this case, react finds out that the array reference is changed. So, it re-renders.
For better understanding, you should read this thread.

Why does the first iteration of my update to an array in state does not appear? It will only appear after the second iteration?

I have an array postArray defined in state on Main.js.
this.state ={
step: 1,
// welcome
qNumber:1,
accountNumber:'',
amount:'',
txNumber:1,
postArray : []
}
I also have a function on Main.js which inserts new array element into postArray:
insertTx =() => {
// save transaction to array state
// create copy of the array
const copyPostArray = Object.assign([],this.state.postArray)
// insert one element into the array
copyPostArray.push({
txNumber: this.state.txNumber+"-"+this.state.accountNumber,
qNumber : this.state.qNumber,
accountNumber : this.state.accountNumber,
amount : this.state.amount
})
// save the values back to array state
this.setState({
postArray:copyPostArray
})
console.log(this.state.postArray)
console.log(this.state.txNumber)
console.log(this.state.qNumber)
console.log(this.state.accountNumber)
console.log(this.state.amount)
}
On CashDeposit.js, postArray is being updated whenever I call InsertTx function below:
continue = e => {
e.preventDefault();
this.props.nextStep();
//increment the txNumber
// this.props.incTxNumber();
this.props.insertTx();
Viewing the postArray on the console.log, it shows an empty array on first iteration. But for the second iteration, it will show the value for the first, on the third iteration will show value for the second and so on. Why does it not update current values?
setState does not happen right away. The state will always be the same values until the next render happens. If you update state, then in the same cycle reference state, you will get the old state. This would make it appear that you are one behind if you run something like:
this.setState(newValues)
console.log(this.state) // old values
Make sure that when you are referencing state you don't rely on a setState from another function. This is where hooks and useEffect come in handy.
The issue you're seeing is caused by the fact that setState does not set the state immediately, you can think of it like an asynchronous operation. So when you try to log the state values, you are getting old values because the state hasn't changed yet.
In order to get access to the new state value, you can pass a callback to setState as a second parameter: this.setState(newState, updatedState => console.log(updatedState))
This is because setState() does not immediately update state. You will not see the updated state until the next time render() is called. Because of how React reconciles, this is pretty fast, because React won't try to build the DOM until all the setState() calls have been shaken out. But it also means that, while you can't see the new state immediately in the console, you can rest assured that you will see it eventually, before it appears in the browser.
It does, however, mean you need to be sure you've got your initial state condition handled in your code. If you don't set up your state in your constructor, you'll have at least one go-around where you'll need to render without undefined state throwing errors, for example.

When exactly do React Hook states get set?

I am trying to understand the order of execution for the following piece of code involving React Hook states:
const App = () => {
const [ searchedCountry, setSearchedCountry ] = useState('');
const [ filteredArr, setFilteredArr ] = useState([]);
const [ filteredLength, setFilteredLength ] = useState(0);
//...
const filterCountries = (event) => {
event.preventDefault();
setFilteredArr(countries.filter(country => country.name.includes(searchedCountry)));
setFilteredLength(filteredArr.length);
console.log("filtered arr length", filtered.length);
}
//...
}
When filterCountries is triggered, setFilteredArr sets the state, filteredArr, to an array filtered by a query. However, when exactly does filteredArr get set?
filteredArr.length returns 0, meaning filteredArr has not been set yet, even after calling setFilteredArr.
At first, I thought by executing setFilteredArr, the component re-renders, causing execution to skip the method calls after setFilteredArr. That would explain why filteredArr.length is 0. However, the console.log is still called, meaning after a component re-renders, the order of execution is actually resumed.
What is going on? When exactly does filteredArr get set?
The thing to remember is: all your state variables are local variables. They only exist for this particular time the component rendered. So on the first render, console.log("filtered arr length", filteredArr.length); is referring to the array that exists on that first render. filteredArr will never be assigned a new array (it can't, it's a const), and unless you mutate the array (which you shouldn't), the length of that array will always be 0.
When you call setFilteredArr, this instructs react to rerender the component. React might do the rendering synchronously, or it might wait to try to batch up changes. When that 2nd render happens, you make a call to useState and get back the new value. This is assigned to a local variable named filteredArr, but this is a completely different variable than the one we had on the first render. The console.log statement in that first render, will have no way to access the variable in the second render. But the second render has access to it, so any logging you do the second time will show the second array.
setState or 'setFilteredLength' is an async operation. After you call it, it will take some time to update the state (as it is async, it will not wait for that update. It will simply execute the next line) so when it executes the console.log the value has not changed -> yet

useEffect pushing value to array only once ReactJs

So basically i started learning ReactJs and am trying to build a chatting app
now here there is section of adding Channels so here is my code
const [Channels, setChannelState] = React.useState([])
let loadedChannels = []
React.useEffect(() => {
setCurrentUser(props.currentUser)
ref.ref('channels').on('child_added', snap =>{
loadedChannels.push(snap.val())
console.log(loadedChannels)
setChannelState(loadedChannels)
})
},[])
so here when i tried to log the loadedChannels Array am getting all the result but when i try to set this array to my state then am getting only one result
how can i resolve this?
You have an empty array [] as a second parameter which runs the function passed to useEffect only once.
You can tell React to skip applying an effect if certain values haven’t changed between re-renders. To do so, pass an array as an optional second argument to useEffect which you have done.
If we pass [] as second parameter this will apply effect only once.
We can pass value inside array, only on change which it will trigger useEffect call.
As per the documentation
If you want to run an effect and clean it up only once (on mount and
unmount), you can pass an empty array ([]) as a second argument. This
tells React that your effect doesn’t depend on any values from props
or state, so it never needs to re-run. This isn’t handled as a special
case — it follows directly from how the dependencies array always
works.
try using something like this:
const [Channels, setChannelState] = React.useState([])
React.useEffect(() => {
let loadedChannels = []
setCurrentUser(props.currentUser)
ref.ref('channels').on('child_added', snap =>{
loadedChannels.push(snap.val())
})
setChannelState([...loadedChannels])
},[])
Hope this helps, Happy coding!!!
The second argument for useEffect is called a dependency array. And when it's empty, it acts like componentDidMount() i.e it runs only once, after the first render.
You also need to be careful about setting state in useEffect, since this might create an infinite loop of re-renders if not done correctly - setCurrentUser. That is not enough code to help any further than that.
This is the perfect time to get into React. Have fun!
Second parameter is passed for conditional rendering. To understand that there are few scenarios which tell us the possible ways to use that.
There are multiple scenarios which will make it easier for you to understand the importance and functionality of the second parameter with the help of the example from demo.
Scenario 1
Second parameter as empty array,
useEffect(() => {
console.log(`You clicked ${count} times`);
},
[ ]) //called only once similar to componentDidMount
If we pass an empty array, it means that we are not interested in monitoring any of the values so that the useEffect won’t be called except on mounting and before un-mounting. This mimic’s the working of componentDidMount and componentWillUnmount, it will only run once.
Scenario 2
Second parameter with value(s),
useEffect(() => {
console.log(`You clicked ${count} times`);
}, [count]); //function inside useEffect will be called when "count" will be updated
The value passed as the second parameter (array) here in our example: count will be responsible for the execution of the function inside the useEffect Hook. If the value inside the array will be updated, then and only then the function will be executed.
What if we have multiple useEffect hooks and we need to update just few of them? Conditional rendering comes in light. Conditional rendering is achieved by passing the second parameter instead of just empty array. The values in the array will be monitored and the useEffect function will only be called when the monitored value(s) is/are updated.
Refer these links for more info
https://stackblitz.com/edit/react-hooks-intro
https://www.c-sharpcorner.com/article/react-hooks-introduction/

useState React hook always returning initial value

locationHistory is always an empty array in the following code:
export function LocationHistoryProvider({ history, children }) {
const [locationHistory, setLocationHistory] = useState([])
useEffect(() => history.listen((location, action) => {
console.log('old state:', locationHistory)
const newLocationHistory = locationHistory ? [...locationHistory, location.pathname] : [location.pathname]
setLocationHistory(newLocationHistory)
}), [history])
return <LocationHistoryContext.Provider value={locationHistory}>{children}</LocationHistoryContext.Provider>
}
console.log always logs []. I have tried doing exactly the same thing in a regular react class and it works fine, which leads me to think I am using hooks wrong.
Any advice would be much appreciated.
UPDATE: Removing the second argument to useEffect ([history]) fixes it. But why? The intention is that this effect will not need to be rerun on every rerender. Becuase it shouldn't need to be. I thought that was the way effects worked.
Adding an empty array also breaks it. It seems [locationHistory] must be added as the 2nd argument to useEffect which stops it from breaking (or no 2nd argument at all). But I am confused why this stops it from breaking? history.listen should run any time the location changes. Why does useEffect need to run again every time locationHistory changes, in order to avoid the aforementioned problem?
P.S. Play around with it here: https://codesandbox.io/s/react-router-ur4d3?fontsize=14 (thanks to lissitz for doing most the leg work there)
You're setting up a listener for the history object, right?
Assuming your history object will remain the same (the very same object reference) across multiple render, this is want you should do:
Set up the listener, after 1st render (i.e: after mounting)
Remove the listener, after unmount
For this you could do it like this:
useEffect(()=>{
history.listen(()=>{//DO WHATEVER});
return () => history.unsubscribe(); // PSEUDO CODE. YOU CAN RETURN A FUNCTION TO CANCEL YOUR LISTENER
},[]); // THIS EMPTY ARRAY MAKES SURE YOUR EFFECT WILL ONLY RUN AFTER 1ST RENDER
But if your history object will change on every render, you'll need to:
cancel the last listener (from the previous render) and
set up a new listener every time your history object changes.
useEffect(()=>{
history.listen(()=>{//DO SOMETHING});
return () => history.unsubscribe(); // PSEUDO CODE. IN THIS CASE, YOU SHOULD RETURN A FUNCTION TO CANCEL YOUR LISTENER
},[history]); // THIS ARRAY MAKES SURE YOUR EFFECT WILL RUN AFTER EVERY RENDER WITH A DIFFERENT `history` OBJECT
NOTE: setState functions are guaranteed to be the same instance across every render. So they don't need to be in the dependency array.
But if you want to access the current state inside of your useEffect. You shouldn't use it directly like you did with the locationHistory (you can, but if you do, you'll need to add it to the dependency array and your effect will run every time it changes). To avoid accessing it directly and adding it to the dependency array, you can do it like this, by using the functional form of the setState method.
setLocationHistory((prevState) => {
if (prevState.length > 0) {
// DO WHATEVER
}
return SOMETHING; // I.E.: SOMETHING WILL BE YOUR NEW STATE
});

Resources