React UseState - using previous state vs not using previous state [duplicate] - javascript

This question already has answers here:
Which approach is good to update State of the Component in React
(2 answers)
Closed 3 years ago.
I was wondering what the difference is between both examples below.
In one example I use the previous state and in the other example I directly use the current value.
They both give me the same results.
In which cases should I use one way over the other?
Thanks in advance.
import React,{useState} from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function App() {
const [count, setCount] = useState(0);
const [count2, setCount2] = useState(0);
return (
<div className="App">
Count: {count}
<button onClick={() => setCount(0)}>Reset</button>
<button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
<button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
<br/>
<br/>
Count: {count2}
<button onClick={() => setCount2(0)}>Reset</button>
<button onClick={() => setCount2(count2 - 1)}>-</button>
<button onClick={() => setCount2(count2 + 1)}>+</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Because those calls to the state setter are in click handlers, your component is guaranteed to be re-rendered before another click is processed. For that reason, in most cases you don't have to use the callback version of the setter, you can directly use your existing state. (Even in concurrent mode.) (Note that if you handle the same click in more than one place [an element and a descendant of it, for instance], and you want both of those handlers to update the value, that's a different matter — see skyboyer's answer for an example of that.)
This is not true for all events (mousemove, for instance, does not have this guarantee), but it's true for click.
I got this information from Dan Abramov on twitter in this thread. At the time, events like click that had this guarantee were called "interactive" events. The name has since changed to "discrete" events. You can find a list in this source file in the React code.
Of course, not all state changes come directly from events. Suppose you have a click handler in your code that does a couple of ajax calls in series and, it happens, updates your value in response to completing each of them. The direct update version will be incorrect even if you've tried to be really thorough with useCallback; the callback version will be correct:
const {useState, useCallback} = React;
function ajaxGet() {
return new Promise(resolve => setTimeout(resolve, 10));
}
function Example() {
const [directValue, setDirectValue] = useState(0);
const [callbackValue, setCallbackValue] = useState(0);
const doThis = useCallback(() => {
setDirectValue(directValue + 1);
setCallbackValue(callbackValue => callbackValue + 1);
}, [directValue, callbackValue]);
const doThat = useCallback(() => {
setDirectValue(directValue + 1);
setCallbackValue(callbackValue => callbackValue + 1);
}, [directValue, callbackValue]);
const handleFirstFulfilled = useCallback(() => {
// ...
doThis();
// ...
return ajaxGet("something else");
}, [doThis]);
const handleSecondFulfilled = useCallback(() => {
// ...
doThat();
// ...
}, [doThat]);
const handleClick = useCallback(() => {
ajaxGet("something")
.then(handleFirstFulfilled)
.then(handleSecondFulfilled)
.catch(error => {
// ...handle/report error...
});
}, [handleFirstFulfilled, handleSecondFulfilled]);
const cls = directValue !== callbackValue ? "diff" : "";
return (
<div className={cls}>
<input type="button" onClick={handleClick} value="Click Me" />
<div>
Direct: {directValue}
</div>
<div>
Callback: {callbackValue}
</div>
</div>
);
}
ReactDOM.render(<Example />, document.getElementById("root"));
.diff {
color: #d00;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
(Disclaimer: That code may be utter rubbish. The point is to see the effect despite having tried to memoize everything. :-) )
For that reason, any time I'm setting a new value that's based on the previous value, I use the callback version unless it's a dedicated click handler or similar, in which case I may go direct.
Getting back to events, concurrent mode makes non-"discrete" events easier to stack up. In the current version of React on cdnjs.com (v16.10.2), I cannot get the following to have different numbers for directValue, callbackValue, and manualValue:
const {useState} = React;
// Obviously this is a hack that only works when `Example` is used only once on a page
let manualValue = 0;
const manualDisplay = document.getElementById("manualDisplay");
function Example() {
const [directValue, setDirectValue] = useState(0);
const [callbackValue, setCallbackValue] = useState(0);
const handleMouseMove = () => {
setDirectValue(directValue + 1);
setCallbackValue(callbackValue => callbackValue + 1);
manualDisplay.textContent = ++manualValue;
};
const different = directValue !== callbackValue || directValue !== manualValue;
document.body.className = different ? "diff" : "";
return (
<div onMouseMove={handleMouseMove}>
Move the mouse rapidly over this element.
<div>
Direct: {directValue}
</div>
<div>
Callback: {callbackValue}
</div>
</div>
);
}
const ex = <Example />;
if (ReactDOM.createRoot) {
document.body.insertAdjacentHTML("beforeend", "<div>Concurrent</div>");
ReactDOM.createRoot(document.getElementById("root")).render(ex);
} else {
ReactDOM.render(ex, document.getElementById("root"));
document.body.insertAdjacentHTML("beforeend", "<div>Legacy</div>");
}
.diff {
color: #d00;
}
<div id="root"></div>
<div>
Manual: <span id="manualDisplay">0</span>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
Maybe that's just me not testing on enough platforms, but I can't get them to diverge in React's "legacy mode." But, using that same code with the experimental release with concurrent mode, it's fairly easy to get the directValue to lag behind the callbackValue and manualValue by waggling the mouse quickly over it, indicating that the event handler is running more than once between renders.

For your example there is no difference. But there are cases when that matters.
const [val, setVal] = useState(0);
return (<div onClick={() => setVal(val + 1)}>
<span onClick={() => setVal(val + 1)}>{val}</span>
</div>);
will increment value only by 1 per click(0 -> 1 -> 2 -> 3). Live Example:
const {useState} = React;
function Example() {
const [val, setVal] = useState(0);
return (<div onClick={() => setVal(val + 1)}>
<span onClick={() => setVal(val + 1)}>{val}</span>
</div>);
}
ReactDOM.render(<Example/>, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
const [val, setVal] = useState(0);
return (<div onClick={() => setVal(oldVal => oldVal + 1)}>
<span onClick={() => setVal(oldVal => oldVal + 1)}>{val}</span>
</div>);
will increment value by 2 per click(0 -> 2 -> 4 -> 6). Live Example:
const {useState} = React;
function Example() {
const [val, setVal] = useState(0);
return (<div onClick={() => setVal(oldVal => oldVal + 1)}>
<span onClick={() => setVal(oldVal => oldVal + 1)}>{val}</span>
</div>);
}
ReactDOM.render(<Example/>, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>

Related

React functional component state variable's setFunction is not working when it is called through another function reference

I have found this error while trying to build another React app. So I am only asking the main issue here in a demo app, I might not be able to change any rendering methods here since it is not the actual project.
Issue in simplified form -> I was building a app where two count will be shown and a + button will be there next to that count value. When the button is clicked the count should be increased by 1. Unfortunately when I try to click on the button the value is increasing only the first time. After that the value is not even changing. But when I am implementing the same using Class component its working as expected.
Functional Component
import React, { useState } from "react";
function Page(props) {
const [count, setCount] = useState(0);
const [content, setContent] = useState({
button: (value) => {
return <button onClick={() => handlePlus(value)}>+</button>;
},
});
function handlePlus(value) {
console.log("value=", value);
const data = count + 1;
setCount((count) => data);
}
return (
<div>
<span>Functional Component Count = {count}</span>
{content.button(10)} // 10 will be replaced with another variable
</div>
);
}
export default Page;
Class Component
import React, { Component } from "react";
class PageClass extends Component {
state = {
count: 0,
content: {
button: (value) => {
return (
<button onClick={() => this.handlePlus(value)}>+</button>
);
},
},
};
handlePlus = (value) => {
console.log("value=", value);
this.setState({ count: this.state.count + 1 });
};
render() {
return (
<div>
<span>Class Component Count = {this.state.count}</span>
{this.state.content.button(10)} // 10 will be replaced with another variable
</div>
);
}
}
export default PageClass;
App.js
import "./App.css";
import Page from "./components/Page";
import PageClass from "./components/PageClass";
function App() {
return (
<div className="App">
<Page />
<PageClass />
</div>
);
}
export default App;
However, If I replace that content state variable with normal const variable type and it is working as expected.
Below is working when I am not using any hooks to render the button.
But this is not helpful for my case.
const content = {
content: () => {
console.log(count);
return <button onClick={() => handlePlus(value)}>+</button>;
},
};
I was trying to create some re-usable components and hence I wanted to have that function in state variable which return button tag, so that I can implements some other logic there.
The value will be missing since you're passing a hard-coded 10.
I'd recommend simplifying the handlePlus to just:
setCount(c => c + 1);
Then set the onclick like so:
<button onClick={handlePlus}>+</button>
And your code will work as expected as you can see in this snippet:
const { useState } = React;
const Example = () => {
const [count, setCount] = useState(0);
const [content, setContent] = useState({
content: (value) => {
return <button onClick={handlePlus}>+</button>;
},
});
function handlePlus(value) {
setCount(c => c + 1);
}
return (
<div>
<span>{count}</span>
{content.content(10)}
</div>
);
}
ReactDOM.render(<Example />, document.getElementById("react"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>
That said, I'd recommend removing the button from the hook, and just render it yourself:
const { useState } = React;
const Example = () => {
const [count, setCount] = useState(0);
function handlePlus(value) {
setCount(c => c + 1);
}
return (
<div>
<span>{count}</span>
<button onClick={handlePlus}>+</button>
</div>
);
}
ReactDOM.render(<Example />, document.getElementById("react"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>
See React documentation about the c => c + 1 syntax

how to get current value from input field in react js

I am trying to get current value from input field, but after onclick I am getting preious value in colsole.
here is my code
import { React, useState } from "react";
const CompoundIntrest = () => {
const [capitalValue, setcapitalValue] = useState(1000);
const ChangeCapital = () => {
setcapitalValue(capitalValue - 100);
};
const Calculate = () => {
console.log(capitalValue);
};
return (
<>
<button
onClick={() => {
ChangeCapital();
Calculate();
}}
>
click
</button>
<input type="number" value={capitalValue} />
</>
);
};
export default CompoundIntrest;
State updates occur asynchronously, so you won't have the updated state value inside the event handler.
You can lift the new value i.e. capitalValue - 100 to a scope from where it can be passed down to both ChangeCapital & Calculate.
const CompoundIntrest = () => {
const [capitalValue, setCapitalValue] = React.useState(1000);
const handleClick = () => {
const newCapitalValue = capitalValue - 100;
ChangeCapital(newCapitalValue);
Calculate(newCapitalValue);
};
const ChangeCapital = (capitalValue) => {
setCapitalValue(capitalValue);
};
const Calculate = (capitalValue) => {
console.log(capitalValue);
};
return (
<React.Fragment>
<button onClick={handleClick}>click</button>
<input
type="number"
value={capitalValue}
onChange={(e) => setCapitalValue(e.target.value)}
/>
</React.Fragment>
);
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<CompoundIntrest />);
<script crossorigin src="https://unpkg.com/react#18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.development.js"></script>
<div id="root"></div>
Note: The state updater function is called synchronously but the state updates happen asynchronously.
This becomes more clear if you update the state by passing a state updater callback, you would see that the callback is fired synchronously. Notice the order of logs in the example below:
function App() {
const [count, setCount] = React.useState(0);
const handleClick = () => {
console.log("Before calling setCount");
setCount((currCount) => {
console.log("Inside setCount");
return currCount + 1;
});
console.log("After calling setCount");
};
return <button onClick={handleClick}>Count: {count}</button>;
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
<script crossorigin src="https://unpkg.com/react#18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.development.js"></script>
<div id="root"></div>
You can use Use useEffect Like this:-
import React,{useState,useEffect} from "react";
const CompoundIntrest = () => {
const [capitalValue, setcapitalValue] = useState(1000);
const ChangeCapital = () => {
setcapitalValue(capitalValue - 100);
};
const Calculate = () => {
console.log(capitalValue);
};
useEffect(()=>{
console.log("afet chage",capitalValue);
},[capitalValue]);
return (
<>
<button
onClick={() => {
ChangeCapital();
Calculate();
}}
>
click
</button>
<input type="number" value={capitalValue} />
</>
);
};
You can use the onChange event in the input field to get current value.
const [currentValue, setCurrentValue] = useState['']
const changeHandler = (e:any) => {
e.preventDefault();
const { value } = e.target
console.log('value', value);
setCurrentValue(value)
}
<input type="string" value={currentValue} onChange={(e:any) => changeHandler(e)}/>
I think, you should add onChange method in input tag like below:
Then you get current value in onClick event in button tag.
import { React, useState } from "react";
const CompoundIntrest = () => {
const [capitalValue, setcapitalValue] = useState(1000);
const ChangeCapital = () => {
setcapitalValue(capitalValue - 100);
};
useEffect(() => {
const Calculate = () => {
console.log(capitalValue);
};
Calculate()
}, [capitalValue])
return (
<>
<button
onClick={() => {
ChangeCapital();
}}
>
click
</button>
<input type="number" value={capitalValue} onChange={(e) => setcapitalValue(e.target.value)} />
</>
);
};
export default CompoundIntrest;
In the case of controlled component, other members have already provided the answer, I just want to give you an idea about uncontrolled component.
Assuming that we are dealing with an uncontrolled component ( "input" element ) then how we can get the value.
1. import { React, useState, useRef, useEffect } from "react";
2.
3. const CompoundIntrest = () => {
4. const [capitalValue, setcapitalValue] = useState(1000);
5. const inputRef = useRef(null);
6.
7. useEffect(() => {
8. console.log(capitalValue);
9. }, [capitalValue]);
10.
11. const ChangeCapital = () => {
12. setcapitalValue(inputRef.current.value - 100);
13. };
14.
15. return (
16. <>
17. <button onClick={ChangeCapital}>click</button>
18. <input ref={inputRef} type="number" />
19. </>
20. );
21. };
22.
23. export default CompoundIntrest;
At line 5, we have created a ref with initial value null using useRef hook of react, which later will be used to store reference of input element.
At line 18, we have assigned the inputRef to the ref of input element, which will be use to get the value from the field.
At line 12, we are getting the value of input as inputRef.current.value .
To check the update in the value of capitalValue state onClick event of button we can use useEffect hook of react ( From Line 7 to Line 9 is doing the same ).
PS : Please let me know if this clear your doubt or not. Thanks for reading the answer.
your code is fine, and your state is successfuly updated, the problem is the timing of calling your console. react handles your code async, it means it starts your changeCapital, and before the change capital function is finished it calls the calculate function, so the value of your state, is the previous value.
you need to call your calculate function somewhere else:
you can call it in a UseEffect hook, this way your function gets called whenever your state has successfuly changed, or
you can call your calculate in 'onchange' event of your input feild
if you want the better solution, the first one is more reactly than the second one

How to make a class value reactive in React

I have a simple class, something like this:
class ReallyHugeClass {
constructor() {
this.counter = 0;
}
increment = () => {
this.counter += 1
}
}
If I use it in the code in a straightforward way it won't keep its state. The class will be recreated every time on render and it's not reactive at all.
const Component = () => {
const instance = new ReallyHugeClass();
return (
<button onClick={instance.increment}>
{instance.counter}
</button>
)
}
Don't rush to say: you don't need the class! Write this:
const Component = () => {
const [counter, setCounter] = useState(0);
return (
<button onClick={() => { setCounter(value => value + 1) }}>
{counter}
</button>
)
}
I used the ridiculously small class example, but the real one is complicated. Very complicated. I can't just split it into the set of useState calls.
Let's go forward. I can wrap the instance to useRef to save its value.
const Component = () => {
const instance = useRef(new ReallyHugeClass());
return (
<button onClick={instance.current.increment}>
{instance.current.counter}
</button>
)
}
The value is saved, but it's still not reactive. I can somehow force the component to rerender by passing the corresponding callback to class, but it looks awkwardly.
What's the right pattern to solve such task in React? It looks that it's quite likely situation.
One solution would be to use useRef and force rendering with a useState. Here an example:
const { useRef, useState } = React;
class ReallyHugeClass {
constructor() {
this.counter = 0;
}
increment() {
this.counter += 1;
console.log(this.counter);
}
}
function App() {
const instance = useRef(new ReallyHugeClass());
const [forceRender, setForceRender] = useState(true);
return (
<button
onClick={() => {
instance.current.increment();
setForceRender(!forceRender);
}}
>
{instance.current.counter}
</button>
);
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<>
<App />
<App />
</>
);
<script src="https://unpkg.com/#babel/standalone/babel.min.js"></script>
<script
crossorigin
src="https://unpkg.com/react#18/umd/react.production.min.js"
></script>
<script
crossorigin
src="https://unpkg.com/react-dom#18/umd/react-dom.production.min.js"
></script>
<div id="root"></div>

ReactJS: setTimeout() not working inside return?

Inside of render(), return(), I am trying to set a timeout but it's not working.
Am I doing something wrong?
{setTimeout(() => {
filtered.length && (
<FilterListContainer
containerHeight={this.state.filterContainerHeight}
>
<FilterListScroll>
<FilterList ref={this.filterListRef}>
{filtered.map((k) => (
<SidebarFilter
key={k}
type={k}
filter={this.props.body_search_filter[k]}
handleChange={this.handleFilterChange}
/>
))}
</FilterList>
</FilterListScroll>
</FilterListContainer>
);
}, 1)}
You've said you don't want that content to appear until "a bit later."
To do that, you'd want to have a state member saying whether to show the content, use that when rendering, and have the setTimeout that changes the state member's value.
For instance, here's an example using hooks:
const { useState, useEffect } = React;
const Example = () => {
const [showList, setShowList] = useState(false);
useEffect(() => {
const handle = setTimeout(() => {
setShowList(true);
}, 800); // Longer delay so you can see it
}, []);
return <div>
<div>Hi there</div>
{showList && <div>This is the list</div>}
</div>;
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Example />);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.0.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.0.0/umd/react-dom.development.js"></script>

React Component is not re-rendering on the first state update

I'm currently working on a very simple survey. The kind that you reply by Yes or No. I have made a list of questions that I have put and extracted in its own file (QuestionsList.js).
Here is my question list:
const QuestionsList = [
"Do you believe in ghosts? ",
"Have you ever seen a UFO? ",
"Can cats jump six times their length? "
]
export default QuestionsList
I have my App:
import './App.css';
import Question from './components/Question';
import QuestionsList from './QuestionsList'
import { useState } from 'react';
function App() {
let questionsList = QuestionsList
const [current, setCurrent] = useState(0)
const [currentQuestion, setCurrentQuestion] = useState(null)
const [answers, setAnswers] = useState([])
const [isStarted, setIsStarted] = useState(false)
const onStartHandler = () => {
setIsStarted(true)
updateCurrentQuestion()
}
const updateCurrentQuestion = () => {
setCurrentQuestion(questionsList[current])
}
const onYesHandler = () => {
setCurrent(current => current += 1)
setAnswers([...answers, 1])
updateCurrentQuestion()
}
const onNoHandler = () => {
setCurrent(current => current += 1)
setAnswers([...answers, 0])
updateCurrentQuestion()
}
return (
<div className="App">
{isStarted ? <Question question={currentQuestion} onYes={onYesHandler} onNo={onNoHandler} /> : null}
<button onClick={onStartHandler}>START!</button>
<button onClick={() => console.log(`Current: ${current}\nCurrent Question: ${currentQuestion}\nAnswers: ${answers}`)}>STATE LOG</button>
</div>
);
}
export default App;
And my Question component:
import React from 'react'
const Question = (props) => {
return (
<div>
<h2>{props.question}</h2>
<div>
<button onClick={props.onYes}>YES</button>
<button onClick={props.onNo}>NO</button>
</div>
</div>
)
}
export default Question
The problem is that whenever I launch the app. The first question shows up, but on the very FIRST click on YES or NO, the state changes and so does the question, but the FIRST click, does not rerender the question. However, every subsequent click does re-render the component. What am I missing?
When you call setCurrentQuestion() it uses the previous value of current because setting the state (which setCurrent does) is async.
You don't need the currentQuestion state, because it's derived from current. Use the current value to get the question from the questionsList.
const { useState } = React;
const Question = (props) => {
return (
<div>
<h2>{props.question}</h2>
<div>
<button onClick={props.onYes}>YES</button>
<button onClick={props.onNo}>NO</button>
</div>
</div>
)
}
function App({ questionsList }) {
const [current, setCurrent] = useState(0)
const [answers, setAnswers] = useState([])
const [isStarted, setIsStarted] = useState(false)
const onStartHandler = () => {
setIsStarted(true)
}
const onYesHandler = () => {
setCurrent(current => current += 1)
setAnswers([...answers, 1])
}
const onNoHandler = () => {
setCurrent(current => current += 1)
setAnswers([...answers, 0])
}
return (
<div className="App">
{isStarted ? (
<Question
question={questionsList[current]}
onYes={onYesHandler}
onNo={onNoHandler} />
)
:
(
<button onClick={onStartHandler}>START!</button>
)
}
</div>
);
}
const QuestionsList = [
"Do you believe in ghosts? ",
"Have you ever seen a UFO? ",
"Can cats jump six times their length? "
];
ReactDOM.render(
<App questionsList={QuestionsList} />,
root
);
<script crossorigin src="https://unpkg.com/react#17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.development.js"></script>
<div id="root"></div>
Set initial state
the simple solution:
replace this
const [currentQuestion, setCurrentQuestion] = useState(null)
in this const [currentQuestion, setCurrentQuestion] = useState(questionsList[current])
BTW
I see some things to improve.
the onYesHandler onNoHandler can be one function that get boolean (true or false for yes or no)
You don't need to save the data you import let questionsList = QuestionsList. you can use it from the import as QuestionsList
but it's looks ok for a beginner :)
const updateCurrentQuestion = (num) => {
setCurrentQuestion(questionsList[num])
}
// within no/yes functions
let num = current;
setCurrent(old => old + 1);
updateCurrentQuestion(num);
This should fix it. UpdateCurrentQuestion is getting past state.

Categories

Resources