Please, See this - https://codesandbox.io/s/morning-grass-z8qrq
https://codesandbox.io/s/blue-flower-wl92u
** the second click, third, fourth, fifth click - menuOpen is true, then again click false - behaves as expected**
let [menuOpen, setMenuOpen] = useState(false);
<div
onClick={() => {
// setMenuOpen(true);
setMenuOpen(!menuOpen); // I's not updated in the First time.
console.log(menuOpen); // First time: false // not updating
>
.......// some code
</div>
Please give me, some answers. I have been trying to solve this problem for Two days. I just can't solve it.
Try this:
export default function App() {
const [menuOpen, setMenuOpen] = useState(false);
return (
<>
<button onClick={() => setMenuOpen(!menuOpen)}>Click</button>
Is menu Open: { menuOpen ? "True": "False"}
</>
);
}
Example demo can be found here.
useState create queues for React core to update the state object of a React component. So the process to update React state is asynchronous for performance reasons. That's why changes don't feel immediate.
Give this a try
setMenuOpen(prevMenuOpenState => !prevMenuOpenState);
or
<div
onClick={() => setMenuOpen(!menuOpen)}
>
I had even this problem in my code. My scenario is as follows:
Its hotel detail page. There is horizontal tab menu of room types. If a hotel has more than 3 types of room, then there is show room button. I am using React Functional components in through the code. hotel detail basic page and room section page are different components created. values are passed to room section components through props.
My problem: When I click to room type further 3rd type, then show room value in function (setSelectedTab()) room component doesn't set at an instant. And hence as function moves further, it doesn't set document.getElementById(id) since show room had not been set. As function (setSelectedTab()) completes in first click it sets the show room to true, but selected tab doesn't set. I had to click 2nd time to set the tab.
solution:
After a long try and error, I settle down to the following:
I declare the function as async and made await the setshowRoom() value.
This solved my complete problem.
async function setSelectedTab(e, data) {
firstScroll += 1;
data >= 2 && await setMenuOpen(true);
if (data >= 0) {
.................
const id = e.href;
const anchor = document.getElementById(id);
..............
..............
}
}
and in room component: showRoom, setshowRoom in useState and calling the setSelectedTab() using props. This solves problem of single click
Drawback: I found delay of 1 second to set this tab.
If anyone have better solution than this without making async await, then please post here.
Thanks.
The Answer is just refactoring the code into class Component without using hooks useState. Using state and setState to update. The Problem will solve.
But If I use useState hooks the problem remains the same Whatever I do with the code.
Related
This question already has answers here:
React - useState - why setTimeout function does not have latest state value?
(2 answers)
Closed 7 months ago.
I have a component that renders a table with objects. This component shows a button that, when pressed, sends the parent a specific object. The parent must set it in the state to display some graphical stuff. The rendering is working correctly, what I don't understand is why I am getting an outdated value after setting the state correctly.
It's not a race condition, React is simply ignoring the updated value of a variable, even when it re-renders the component correctly.
A minimal example:
import { useState } from "react";
import { SomeComponent } from "./SomeComponent";
export default function App() {
const [currentID, setCurrentID] = useState(null);
function getData() {
console.log("Getting data of: ", currentID); // PROBLEM: this is null
}
function setAndRetrieveData(value) {
setCurrentID(value);
// Just to show the problem and discard race conditions.
setTimeout(() => {
getData();
}, 1500);
}
return (
<div className="App">
<h1>Current ID: {currentID}</h1> {/* This works fine */}
<SomeComponent getInfoFor={setAndRetrieveData} />
</div>
);
}
SomeComponent:
export function SomeComponent(props) {
const randomID = 45;
return <button onClick={() => props.getInfoFor(randomID)}>Get info</button>;
}
Even with solutions like useStateCallback the problem persists.
Is there a way to do this without having to use the awful useEffect which is not clear when reading the code? Because the logic of the system is "when this button is pressed, make a request to obtain the information", using the hook useEffect the logic becomes "when the value of currentID changes make a request", if at some point I want to change the state of that variable and perform another action that is not to obtain the data from the server then I will be in trouble.
Thanks in advance
I think this is an issue with the way Javascript closures work.
When you execute a function, it gets bundled with all the data that pertains to it and then gets executed.
The issue is that you call this:
setTimeout(() => {
getData();
}, 1500);
inside setAndRetrieveData(value).
Even though it's inside a setTimeout, the getData() function has been bundled with the information it needs (currentID) at that point in time, not when it actually runs. So it gets bundled with the currentId before the state update takes place
Unfortunately, I would recommend using useEffect. This is the best way to ensure you avoid issues like this and any potential race conditions. Hopefully someone else can provide a different approach!
when setAndRetrieveData is called it sets a state that leads to the component being rerendered to reflect the new state. When the timeout finishes The function getData was created in the previous render. And thus only has access to the state variable from the previous render. That now is undefined.
what you could try is using a useEffect hook that that listens to changes of
currentID.
useEffect(() => {
const timeoutId = setTimeout(() => {
// Do something with the updated value
},1000);
return () => {
// if the data updates prematurely
// we cancel the timeout and start a new one
clearTimeout(timeoutId);
}
},[currentID])
I'm trying to create a basic multi-stage web form in Javascript. I wanted to accomplish this kind of like the forms section of Full Stack Open, by creating a collection (an array) of questions, then displaying them as labels on my web page, filtered so that only the appropriate ones appeared at certain times. For example, when you first visit the page, it would say "The next few questions will assess your budget situation", then after pressing start - it would transition to the first question, then pressing next, to the second, and so on.
I thought I accomplished this the correct way, by displaying the filtered collection (phase is initialized to 0 outside of the app):
const questionPhase = () => {
if (phase < 3){
phase = phase + 1;
}
else if(phase == 3){
phase = 0;
addToBudget(attribute);
}
console.log(phase);
}
return (
<div>
<h3> Please answer some questions to start</h3>
<ul>
{questions.filter(question => question.phase === phase).map(question =>
{ < label > {question.script}
<input type="text"
question = {question.attribute}
value={question.attribute}
onChange={handleInput}
/>
</label>})}
</ul>
<button onClick={questionPhase}> {phase === 2 ? 'Next' : 'Submit'} </button>
</div>
)
I've done some logging and determined that phase actually is changing every time I press the button at the bottom. But what doesn't happen, is either the questions (and labels) displaying, or the lettering on the buttons changing.
I'm not sure what I've done wrong? I'm certain there's some subtle aspect of the control flow I've missed but I don't know what - I figured that, as explained in FSO, the app is continually being run through every time there's a change by pressing a button or something, which should be the event created by the button press.
Thank you in advance for any help
appendix: here is the questionsList class (from which questions is imported) and the event handler:
import React from 'react'
const questions = [
{
phase: 1 ,
script: 'What is your monthly income?',
attribute: "income"
},
{
phase: 2,
script: 'what are you monthly expenses?',
attribute: "expenses"
}
]
export default questions
const handleInput = (event) => {
const value = event.target.value;
console.log('valued!')
setAttribute({
...attribute,
[event.target.question]: value
})
}
The only thing that will trigger a re-render in React is a change in state, so if a variable's change should cause a re-render, you should stick it in state.
You can change your questionPhase component to a class or function (same thing), and then in the constructor, define
this.state = {phase};
Then this.state.phase will equal whatever phase was when the component instantiated. You'll want to change the rest of the component to use that variable. The correct way to change its value and trigger a re-render is with a call to setState.
That's the way I would do it; although, it would be easier to just call forceUpdate. That will make react re-render. It's not the react way though. The entire purpose of react is to strongly tie the UI to the underlying state, so I wouldn't recommend using forceUpdate.
This is a difficult one to explain so I will do my best!
My Goal
I have been learning React and decided to try build a Todo List App from scratch. I wanted to implement a "push notification" system, which when you say mark a todo as complete it will pop up in the bottom left corner saying for example "walk the dog has been updated". Then after a few seconds or so it will be removed from the UI.
Fairly simple Goal, and for the most part I have got it working... BUT... if you quickly mark a few todos as complete they will get removed from the UI and then get re-rendered back in!
I have tried as many different ways of removing items from state as I can think of and even changing where the component is pulled in etc.
This is probably a noobie question, but I am still learning!
Here is a link to a code sandbox, best way I could think of to show where I am at:
Alert Component State/Parent
https://codesandbox.io/s/runtime-night-h4czf?file=/src/components/layout/PageContainer.js
Alert Component
https://codesandbox.io/s/runtime-night-h4czf?file=/src/components/parts/Alert.js
Any help much appreciated!
When you call a set function to update state, it will update from the last rendered value. If you want it to update from the last set value, you need to pass the update function instead of just the new values.
For instance, you can change your setTodos in your markComplete function to something like this.
setTodos(todos => todos.map((todo) => {
if (id === todo.id) {
todo = {
...todo,
complete: !todo.complete,
};
}
return todo;
}));
https://codesandbox.io/s/jovial-yalow-yd0jz
If asynchronous events are happening, the value in the scope of the executed event handler might be out of date.
When updating lists of values, use the updating method which receives the previous state, for example
setAlerts(previousAlerts => {
const newAlerts = (build new alerts from prev alerts);
return newAlerts;
});
instead of directly using the alerts you got from useState.
In the PageContainer.js, modify this function
const removeAlert = (id) => {
setAlerts(alerts.filter((alert) => alert.id !== id));
};
to this
const removeAlert = (id) => {
setAlerts(prev => prev.filter((alert) => alert.id !== id));
};
This will also fix the issue when unchecking completed todos at high speed
I have problem with pager click to re-render list users. Please check on my logic below
Firstly, I list all users by calling an action is ListUsersAction like this:
const ListUsers = props => {
useEffect(() => {
var resPerPage = configList.users.resPerPage;
props.ListUsersAction(resPerPage, 1);
},[]);
.....
.....
}
I used a reducer listUsersReducer to store users. I call it like that:
if (props.listUsersReducer.thanhvien.length > 0) {
const users = props.listUsersReducer.thanhvien;
//Then render all user here.
users.map((user)=>....);
//I make a pagination to change users list by click on each page.
<Pagination datasend={datasend} />
}
I has no problem from first load. And problem occur when I click 6-7 times on pagination to call LoadUsersAction. It start slowly and make page lag.
Action called every time click page change.
I do not know anything I made wrong. Can you help me to check it. Thank you so much.
Problem is solved. It is not related to redux. It is store.subscribe render multiple times. Just add it in useEffect and it is solved.
I have a function that changes the screen and sets the state at the same time that works given below (initial state of weapon is null):
var { navigate } = this.props.navigation;
clickM16 = () => {
this.setState({
weapon: "M16"
});
navigate("Second", {weapon: this.state.weapon});
}
And I am calling this on my second screen via {this.props.navigation.state.weapon}, but the state doesn't seem to update to M16 until I go back and click the button again.
I have console logged both above and below the setState function and on the first click it always gives me null but M16 when I go back and click it again.
Can I not run setState at the same time as navigating between screens? If I can what am I doing wrong.
TLDR: I'm trying to set state and change page in same function so I can then display the new state on the new page as text. The state change doesn't happen until the second click of the button.
Try putting a small timeout for the navigate. The state change may not be complete when you hit the navigate instruction
var { navigate } = this.props.navigation;
clickM16 = () => {
this.setState({
weapon: "M16"
});
setTimeout(() => navigate("Second", {weapon: this.state.weapon}),20);
}
State is supposed to be used as a helper to handle a small amount of data inside your component. The state life cycle ends as the component it belongs completely unmount. Also, note that setState is an asynchronous function, so you must not rely on React to handle sync situations for you. Updating your state will also make your component rerender, so you should use it carefully to avoid loss memory unnecessarily.
If you just want to pass data from a component to another, in this case using navigation props is enough, like this navigate("Second", {weapon: 'M16'});. You don't need to update your state to then be able to pass this data further. In fact, it makes no sense to update your state before navigation, since the current state itself will be lost in the next screen.
If you need to share the exact same state prop between more components, which doesn't seem to be the case, maybe you should consider using another approach, like Redux (https://redux.js.org/).
I recommend you to read the official docs for more detailed info:
https://reactjs.org/docs/react-component.html#state
https://reactjs.org/docs/state-and-lifecycle.html
Hope it helps
Edit:
Based on the information you provided below, if weapon will be an array, for example, and you need to push a new value to it before navigation, you should not use setState, try this instead:
const { navigate } = this.props.navigation;
clickM16 = () => {
const { weapon } = this.state;
weapon.push('M16');
navigate("Second", { weapon });
}
Hope it helps
I will give another suggestion:
var { navigate } = this.props.navigation;
clickM16 = () => {
this.setState({
weapon: "M16"
});
let sendPara = this.state.weapon
navigate("Second", {weapon: sendPara});
}
Recive parameter in respective component.
catName={this.props.navigation.state.params.sendPara}
I hope this may help you.