I'm getting the selected checkbox name into an array. Then, I want to use it later for some calculations. but when I try to get the values it's giving the wrong values. I want to allow users to select the ticket count by using the +/- buttons. following is my code
Code
Getting the selected seat from the checkboxes
const [seatCount, setSeatCount] =React.useState("");
const [open, setOpen] = React.useState(false);
const [counter, setCounter] = React.useState(0);
var seatsSelected = []
const handleChecked = async (e) => {
let isChecked = e.target.checked;
if(isChecked){
await seatsSelected.push(e.target.name)
} else {
seatsSelected = seatsSelected.filter((name) => e.target.name !== name);
}
console.log(seatsSelected);
}
calculations happening on a dialog
const handleClickOpen = () => { //open the dialog
setOpen(true);
console.log(seatsSelected);
const seatTotal = seatsSelected.length
console.log(seatTotal)
setSeatCount(seatTotal)
console.log(seatCount)
};
const handleClose = () => { //close the dialog
setOpen(false);
};
const handleIncrement = () =>{ //increse the count
if(counter < seatsSelected.length){
setCounter(counter + 1)
} else {
setCounter(counter)
}
}
const handleDecrement = () =>{ //decrese the count
if(counter >= seatsSelected.length){
setCounter(counter - 1)
} else {
setCounter(counter)
}
}
Setting the state in React acts like an async function.
Meaning that the when you set the state and put a console.log right after it, like in your example, the console.log function runs before the state has actually finished updating.
Which is why we have useEffect, a built-in React hook that activates a callback when one of it's dependencies have changed.
Example:
useEffect(() => {
console.log(seatCount);
}, [seatCount);
The callback will run every time the state value changes and only after it has finished changing and a render has occurred.
Related
I recently started using useEffect hook, and I'm facing some issues. This is a simple App component that renders an input field and submit button. On hitting submit selectItem is called which in turn calls an async function getNames(). getNames function checks if there is an existing entry, if so it returns, otherwise it calls 3rd party API getNewNames() to get newNames. I tried setting the state with this newNames field, but it seems like it is undefined in first render. But after the first render it is defined. How do I make sure that I have newNames field, so that it doesn't return undefined in any renders?
const App = () => {
const [namesArr, setNamesArr] = useState([])
const [name, setName] = useState('')
useEffect (()=> {
console.log('Inside use Effect')
}, [namesArr])
const changeInput = (val) => {
setName(val)
}
const selectItem = async() => {
const returnedVal = await getNames()
// ReturnVal is empty in first render, but filled in second render.
/
}
const getNames = async() =>{
const existingNames = namesArr.find((name)=> name === name)
if(existingNames){
return 'We have an entry'
}
else{
console.log(`Names are not reloaded properly, need to re-render`)
const newNames = await getNewNames() // this is
setName((oldNames)=> [...oldNames, newNames])
return namesArr
}
}
return <div>
<input value={name} onChange={(e)=> changeInput(e.target.value)}></input>
<button onClick={()=> selectItem()}></button>
</div>
}
I think your problem is related to the setName method. In the line after setName console.log(namesArr) will be undefined. So how can we fix this?
const getNames = async() =>{
const existingNames = namesArr.find((name)=> name === name)
if(existingNames){
return 'We have an entry'
}
else{
const newNames = await getNewNames();
// We created our new list outside
// If x is a list, prepend an ellipsis ...newNames
const newList = [...namesArr, newNames];
// setName string is a state. Incorrect state updating.
// setNamesArr instead of setName
setNamesArr(newList)
return newList;
}
}
Now there are two possibilities here.
returnedVal === array or returnedVal === 'We have an entry'
const selectItem = async() => {
const returnedVal = await getNames();
console.log(returnedVal); // array or string.
}
const [filter, setFilter] = useState([]);
let brandList = [];
const handleCheckBox = (e) => {
const { value, checked } = e.target;
if (checked) {
brandList.push(value);
} else {
let ind = brandList.indexOf(value);
brandList.splice(ind, 1);
}
console.log(brandList);
setFilter([...brandList]);
};
When I try to push an element to an array but it takes only one rest of the elements are got removed automatically. It happens when I using the useState hook.
Please checkout the image below:
brandList is redeclared an empty array each render cycle. This is why you only ever see that last filter item value added.
Use the filter state directly.
Example:
const [filter, setFilter] = useState([]);
const handleCheckBox = (e) => {
const { value, checked } = e.target;
if (checked) {
setFilter(filter => [...filter, value]);
} else {
setFilter(filter => filter.filter(el => el !== value));
}
};
I am using a context like the following:
const placeCurrentOrder = async () => {
alert(`placing order for ${mealQuantity} and ${drinkQuantity}`)
}
<OrderContext.Provider
value={{
placeCurrentOrder,
setMealQuantity,
setDrinkQuantity,
}}
>
and I'm calling this context deep down with something like this (when the user clicks a button):
const x = () => {
orderContext.setMealQuantity(newMealQuantity)
orderContext.setDrinkQuantity(newDrinkQuantity)
await orderContext.placeCurrentOrder()
}
Sort of like I expect, the state doesn't update in time, and I always get the previous value of the state. I don't want to have a useEffect, because I want control over exactly when I call it (for example, if mealQuantity and drinkQuantity both get new values here, I don't want it being called twice. The real function is far more complex.)
What is the best way to resolve this? I run into issues like this all the time but I haven't really gotten a satisfactory answer yet.
You can set them in a ref. Then use the current value when you want to use it. The easiest way is probably to just create a custom hook something like:
const useStateWithRef = (initialValue) => {
const ref = useRef(initialValue)
const [state, setState] = useState(initialValue)
const updateState = (newState) => {
ref.current = typeof newState === 'function' ? newState(state) : newState
setState(ref.current)
}
return [state, updateState, ref]
}
then in your context provider component you can use it like:
const [mealQuantity, setMealQuantity, mealQuantityRef] = useStateWithRef(0)
const [drinkQuantity, setDrinkQuantity, drinkQuantityRef] = useStateWithRef(0)
const placeOrder = () => {
console.log(mealQuantityRef.current, drinkQuantityRef.current)
}
You can also just add a ref specifically for the order and then just update it with a useEffect hook when a value changes.
const [drinkQuantity, setDrinkQuantity] = useState(0)
const [mealQuantity, setMealQuantity] = useState(0)
const orderRef = useRef({
drinkQuantity,
mealQuantity
})
useEffect(() => {
orderRef.current = {
drinkQuantity,
mealQuantity,
}
}, [drinkQuantity, mealQuantity])
const placeOrder = () => {
console.log(orderRef.current)
}
I'm trying to setup an onKeyPress event listener and I'm confused as to why the initial value is undefined and then the value I want. The data is added on mount (see x in console). Why am I unable to immediately capture it and instead get an initial undefined, especially since it clearly already exists in state?
useEffect(() => {
console.log('x', multipleChoice); <-- logs the array of objects
const handleKeyPress = ({ key }) => {
const index = Number(key) - 1;
if (key === '1') {
console.log(multipleChoice[index]); <-- logs undefined, then logs object
}
};
window.addEventListener('keydown', (e) => handleKeyPress(e));
return () => {
window.removeEventListener('keydown', (e) => handleKeyPress(e));
};
}, [allCards, currentCard, multipleChoice]);
LocalState
const [currentCard, setCard] = useState(0);
const [multipleChoice, setMultipleChoice] = useState([]);
// allCards is passed as a prop on page load from the parent
When the user guesses an answer correctly the currentCard is incremented by 1
UseEffect that sets multipleChoice
useEffect(() => {
const generateMultipleChoice = (words: Word[]) => {
const possibleAnswers = words.reduce(
(accum: Word[]) => {
while (accum.length < 4) {
// randomly select words from pool
const index = getRandomInt(0, allCards.length - 1);
const randomWord = allCards[index];
// verify current hand doesn't already have that word
if (!accum.includes(randomWord)) {
accum.push(randomWord);
}
}
return accum;
},
// default with the current card already in the hand
[allCards[currentCard]]
);
// return the hand with the matching card and (3) other cards from pool
return possibleAnswers;
};
const shuffledCards = shuffle(generateMultipleChoice(allCards));
setMultipleChoice(shuffledCards);
}, [allCards, currentCard]);
screenshot of console
This is it:
// initial state
const [multipleChoice, setMultipleChoice] = useState([]);
// therefore, initially, if index is any Number
console.log(multipleChoice[index]) // undefined
The object is returned, only until the calculation is finished...
useEffect(() => {
// ...
// this has to run before `multipleChoice` is updated to return that object
const shuffledCards = shuffle(generateMultipleChoice(allCards));
setMultipleChoice(shuffledCards);
}, [allCards, currentCard]);
I have a header that I want to hide on scroll down and show on scroll up.
To do that, I saved the scrolling position as prevScrollPos in the state to compare it to the current scrolling position onscroll, and then update prevScrollPos to the current:
const [visible, setVisible] = React.useState(true);
const [prevScrollPos, setPrevScrollPos] = React.useState(window.pageYOffset);
const handleScroll = () => {
const scrollPos = window.pageYOffset;
const visible = scrollPos < prevScrollPos;
setVisible(visible);
setPrevScrollPos(scrollPos);
}
The problem is that, for some reason PrevScrollPos doesn't get updated.
Pen: https://codepen.io/moaaz_bs/pen/jgGRoj?editors=0110
You need to modify your useEffect function:
React.useEffect(() => {
window.addEventListener('scroll', handleScroll);
return () => {
window.removeEventListener('scroll', handleScroll);
};
});
Basically you don't have access to prevScrollPos in your handler, therefore prevScrollPos inside the listener will always return 0. To solve this, the dependency array should not be present.
-> + Do not forget to remove the event listener after adding it. :-)
Can you try this:
const [visible, setVisible] = React.useState(true);
const [prevScrollPos, setPrevScrollPos] = React.useState(window.pageYOffset);
const handleScroll = () => {
const scrollPos = window.pageYOffset;
const visible_new = scrollPos < prevScrollPos;
setVisible(visible_new);
setPrevScrollPos(scrollPos);
}