setState inside setTimeout react hooks - reactjs

I am currently trying to build a rock-paper-scissor and what I intend to achieve are this logic:
after the start button clicked, a player has 3seconds to pick a weapon, if not, a random weapon will be picked for the player.
The problem:
When I picked a weapon under the 3seconds, it works just fine. But, when I intentionally let the setTimeout triggered, it is not updating the state automatically. I suspected the if conditions are not met, but I don't know why that happen.
Here is the code snippet:
//custom hooks//
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
const weapons= ['rock', 'weapon', 'scissors']
const App = () => {
const [p1Weapon, setp1Weapon] = useState("");
const prevWeapon = usePrevious(p1Weapon);
const getPlayerTimeout = (playerRef, setPlayer, time) => {
setTimeout(() => {
if (playerRef === "") {
setPlayer(weapons[Math.floor(Math.random() * weapons.length)]);
}
}, time);
};
const startGame = () => {
getPlayerTimeout(prevWeapon, setp1Weapon, 3000);
}
return (
...
<div>
<button
className="weaponBtn"
onClick={() => {
setp1Weapon("rock");
}}
>
rock
</button>
<button className="weaponBtn" onClick={() => setp1Weapon("paper")}>
paper
</button>
<button className="weaponBtn" onClick={() => setp1Weapon("scissors")}>
scissor
</button>
<button type="button" onClick={startGame}>
Start!
</button>
</div>
)
Thanks!

if all you want to do is set a state after x time you can do this very easily like this
this.setState({isLoading: true},
()=> {window.setTimeout(()=>{this.setState({isLoading: false})}, 8000)}
);
this should set the value of isLoading to false after 8 seconds.
I hope it helps

Related

Click event added in useEffect after changing state by onClick is executed in same moment

React 18 changed useEffect timing at it broke my code, that looks like this:
const ContextualMenu = ({ isDisabled }) => {
const [isExpanded, setIsExpanded] = useState(false);
const toggleMenu = useCallback(
() => {
if (isDisabled) return;
setIsExpanded((prevState) => !prevState);
},
[isDisabled],
);
useEffect(() => {
if (isExpanded) {
window.document.addEventListener('click', toggleMenu, false);
}
return () => {
window.document.removeEventListener('click', toggleMenu, false);
};
}, [isExpanded]);
return (
<div>
<div class="button" onClick={toggleMenu}>
<Icon name="options" />
</div>
{isExpanded && <ListMenu />}
</div>
);
};
The problem is, toggleMenu function is executed twice on button click - first one is correct, it's onClick button action, which changes state, but this state change executes useEffect (which adds event listener on click) and this click is executed on the same click, that triggered state change.
So, what should be correct and most "in reactjs spirit" way to fix this?
Your problem is named Event bubbling
You can use stopPropagation to fix that
const toggleMenu = useCallback(
(event) => {
event.stopPropagation();
if (isDisabled) return;
setIsExpanded((prevState) => !prevState);
},
[isDisabled],
);

When a check is checked, a button is shown

I'm using React and I have a list, and each item in the list with a check.
And I want that when there is at least one check in true, a button is shown (it is to delete all the checks).
I am using useReducer() to store the list.
This is my code, but it doesn't work.
const init = () => {
return JSON.parse(localStorage.getItem('todos')) || [];
}
const [todos, dispatch] = useReducer(todoReducer, [], init)
const [todoDelete, setTodoDelete] = useState(0)
useEffect(() => {
localStorage.setItem('todos', JSON.stringify(todos));
todos.map(todo => {
todo.check ? setTodoDelete(todoDelete+1) : setTodoDelete(todoDelete-1);
})
}, [todos])
returm (
code...
{
(todoDelete!==0)
&& <button
className='btn btn-danger btn-sm h-50' onClick={handleDeleteChecked}>
Delete
</button>
}
)
What interests me is that when there is more than one check, a button is added, otherwise it is removed. How do i do it?
Change this
todo.check ? setTodoDelete(todoDelete+1) : setTodoDelete(todoDelete-1);
To this
todo.check ?? setTodoDelete(prev => prev+1);

Props is updating before useEffect side effect

I have the following component which shows a question, there's a button within it that allows you to reveal the answer, this is handled through the revealedResults property/state.
const Question = ({
item
}: {
item: QuestionType;
}) => {
const [revealedResults, setRevealedResults] = useState(false);
const { question, answers } = item;
useEffect(() => {
setRevealedResults(false);
}, [item]);
const handleResultReveal = () => {
setRevealedResults(true);
};
return (
<section>
<h1>Question: {question}</h1>
<button onClick={() => handleResultReveal()}>Reveal Answer</button>
<div>
{revealedResults && answers.map((answer) => <p>{answer}</p>)}
</div>
</section>
);
};
export default Question;
const Questionaire = () => {
const [question, setQuestion] = useState(questions[0]);
const [correctAnswers, setCorrectAnswers] = useState(0);
const [incorrectAnswers, setIncorrectAnswers] = useState(0);
const handleQuestionAnswer = (isCorrect: boolean): void => {
if (isCorrect) {
setCorrectAnswers(correctAnswers + 1);
} else {
setIncorrectAnswers(incorrectAnswers + 1);
}
setQuestion(questions[1]);
};
return (
<>
<Question item={question} />
<section>
<div>
<p> Did you get the answer correct?</p>
<button onClick={() => handleQuestionAnswer(true)}>Yes</button>
<button onClick={() => handleQuestionAnswer(false)}>No</button>
</div>
</section>
</>
);
};
export default Questionaire;
The question updates through the item prop. The idea is that when the item prop updates setRevealedResults is ran again to hide the revealed result of the next question.
The problem I'm having is that the prop of the new question is being flashed right before the useEffect side effect is being ran. You can see this here:
What is the correct way to deal with this?
useEffect runs after the render is done. That's why you see the page change for a moment there.
Try to use useMemo instead. It should update during the render.

Adding Two Click Handlers to 1 Function both using React hooks useState

I have a button "Add to Cart" and I would like it to do two things when clicked. I want it to add an item to the cart and I also want it to Change the text to "added" for 1 second.
The problem is if I call onClick twice the second function overrides the first.
If I put both click handlers into 1 function and then call that in 1 single onClick the only the function adding things to the cart works.
Where am I going wrong?
const [variant, setVariant] = useState({ ...initialVariant })
const [quantity, setQuantity] = useState(1)
const {
addVariantToCart,
store: { client, adding },
} = useContext(StoreContext)
const handleAddToCart = () => {
addVariantToCart(productVariant.shopifyId, quantity)
}
const text = "Add To Cart";
const [buttonText, setButtonText] = useState(text);
useEffect(() => {
const timer = setTimeout(() => {
setButtonText(text);
}, 1000);
return () => clearTimeout(timer);
}, [buttonText])
const handleClick = () => {
setButtonText("Added");
handleAddToCart();
}
return (
<>
<button
className="add"
type="submit"
disabled={!available || adding}
onClick={handleClick}
>
Add to Cart
</button>
{!available && <p>This Product is out of Stock!</p>}
</>
you need to use the buttonText inside the button as below, however, in your code you have used the hard text Add to Cart.
<button
className="add"
type="submit"
disabled={!available || adding}
onClick={handleClick}
>
{buttonText}
</button>

How can I change 2 different variables independently with a single function in React?

I'm new to React and I have a short and stupid question, but my poor phrasing makes it so that I haven't been able to find the answer by searching for it.
Basically, I have 2 password fields. I want to show and hide each one independently, but I would like a more elegant way than having 2 different functions with their own variables like this:
const [showPassword1, setShowPassword1] = useState(false);
const [showPassword2, setShowPassword2] = useState(false);
const togglePasswordVisiblity1 = () => {
setShowPassword1(showPassword1 ? false : true);
};
const togglePasswordVisiblity2 = () => {
setShowPassword2(showPassword2 ? false : true);
};
With the respective buttons below:
<span onClick={togglePasswordVisiblity1}>Show/Hide</span>
<span onClick={togglePasswordVisiblity2}>Show/Hide</span>
I'm sure there's a way to regroup these into a single function that changes the right variable based on which span is clicked, but I haven't had any luck finding the syntax. Sorry again for this question, hopefully it can be answered quickly!
Thanks in advance for your help.
You can try to use an array for the state. Check this sandbox demo:
import React, { useState } from "react";
export default function App() {
const [showPassword, setShowPassword] = useState([false, false]);
return (
<div className="App">
<button
onClick={() => setShowPassword([!showPassword[0], showPassword[1]])}
>
{JSON.stringify(showPassword[0])}
</button>
<button
onClick={() => setShowPassword([showPassword[0], !showPassword[1]])}
>
{JSON.stringify(showPassword[1])}
</button>
</div>
);
}
Refactored version by extracting state update into a function:
import React, { useState } from "react";
export default function App() {
const [showPassword, setShowPassword] = useState([false, false]);
const togglePassword = (idx) => {
const newShowPassword = [...showPassword];
newShowPassword[idx] = !newShowPassword[idx]; // toggle
setShowPassword(newShowPassword); // update the state
};
return (
<div className="App">
<button onClick={() => togglePassword(0)}>
{JSON.stringify(showPassword[0])}
</button>
<button onClick={() => togglePassword(1)}>
{JSON.stringify(showPassword[1])}
</button>
</div>
);
}
const [state , setState] = useState({
showPassword1:false,
showPassword2: false
})
const togglePasswordVisiblity1= e => {
const {name , value} = e.target
setState( prevState => ({
...prevState,
[name]: prevState[name] ? false : true
}))
}
//
<span name='showPassword1' onClick={togglePasswordVisiblity1}>Show/Hide</span>
<span name='showPassword2' onClick={togglePasswordVisiblity1}>Show/Hide</span>

Resources