Why the updated count value is not logged while clicking the button. It always logs the previous count value on button click. Here is the code -
import { useState } from "react";
export default function App() {
const [count, setCount] = useState(0);
function updateCount() {
setCount(count + 1);
console.log(count);
}
return (
<div className="App">
<div>
<p>You clicked {count} times</p>
<button onClick={() => updateCount()}>Show alert</button>
</div>
</div>
);
}
on react when your state depends on old state value you must update it like this :
function updateCount() {
setCount(oldCount=>oldCount+ 1);
console.log(count);
}
and you must remember updating the state is not instant action it is super fast, but it take few milliseconds
I am learning React recently and I was facing the exact same problem as you. The reason console.log gives you the previous value as mentioned by Mohammad is because in the same method you are logging and updating the value and they happen together. If you simply put the console.log below the function then it will show you the updated one every time as it finished updating the value before logging.
const incrementCount = (increment) => {
setCount(count + increment);
};
console.log(count);
Hope this helps!
If you want to always log the latest value, you could just do this:
import { useState } from "react";
export default function App() {
const [count, setCount] = useState(0);
console.log(count);
function updateCount() {
setCount(prevCount => prevCount + 1);
}
return (
<div className="App">
<div>
<p>You clicked {count} times</p>
<button onClick={() => updateCount()}>Show alert</button>
</div>
</div>
);
}
The reason why in your code the log function logs an outdated value is because of closure - the count value is a const, and it is set when the component is first rendered. So when you click the button, the value of count is not the new value, but the one it was when it was first rendered (i.e. -> always going to be one less than you expect).
In this code, log happens on every re-render. Since clicking the button calls setCount, the state changes, and a re-render occurs. When that happens, the log is executed, and you get the latest value :)
This is the way that the React lifecycle works. The console.log of your value will use the value of count at the time of render.
Render --> Execute ALL functions with the given state values --> Check for state changes --> Render --> Execute ALL functions with the new state values
Each render is with STATIC values.
If you want to see an output to the console of your count, you'll need to use a useEffect.
Related
I am trying to use a React function component to have a number increment + 1 on keydown but instead of increasing by one (i.e. 1,2,3,4,5) it returns it doubles the number and then increases by on (i.e. 1,3,7,15,31). How Do I get this to react correctly?
const Display = (props) => {
return(
<div id="display">{props.text}</div>
)
}
const App = () => {
[displayText, setDisplayText] = React.useState(0);
window.addEventListener('keydown',(e)=>{
setDisplayText(displayText + 1)
})
return (
<div class="container">
<Display text={displayText} />
</div>
)
}
It has already been answered here:
Increase and decrease the number using addEventListener keydown in React
CODE
export default function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
const keyDownCallback = function (e) {
switch (e.keyCode) {
case 38:
setCount((count) => count + 1);
break;
case 40:
setCount((count) => count - 1);
break;
}
};
document.addEventListener("keydown", keyDownCallback);
return () => document.removeEventListener("keydown", keyDownCallback);
}, []);
return (
<div>
<p>You clicked {count} times</p>
</div>
);
}
you can remove the react strict mode from app.js file to see the correct results.Its that simple.
this error is caused due to strict mode.
Anytime you call setState in a react component, it triggers a re-render of that updated states component, and all the subsequent children of that component.
In your example, you're attaching an event listener to the window object, and calling setState whenever the client presses a key. This can cause inconsistencies in the number rendered in your component, and the actual value of the count state.
To really understand the difference, you can try console logging the value of the state and observing the differences shown in your component, and what's being logged to the console.
Side-note: As a general rule of thumb with event listeners, you want to clean-up the event listeners so you don't have any unwanted side-effects in your component tree.
Here's a good article about rendering in react
And as always, the official docs are a good place to dive deeper on best practices and behaviors, cheers!
I've been learning react over the last few days and for the most part it makes sense, however there is one thing that is stumping me.
If I have
A Parent element with some state variables and a callback method
A child element that takes a callback method as a prop
The callback method relies on some piece of state that is in the parent element
I don't want to re-create the view object every time any state changes
Every time I try to do this, it seems like the child element is calling some older version of the parent element (presumably the instance of the parent that actually created the child) instead of the current version.
I'm getting the feeling that what I want is just wrong on a fundamental level and isnt The React Way
The reason that I am trying to do this is that my main parent contains 17 divs, each of which represent a key on a musical instrument, and each of which contains at least 20-30 divs. The lowest div (of which there are at least a few hundred) has an onClick event that I want to modify the functionality of based on whether modifier keys are held down (shift, control etc).
Currently I have Raised the state of the shiftPressed to be on the single parent element then passed down the value of that into each child through props, however re-rendering hundreds of divs whenever a user pushes shift takes quite a while.
I've made a code sandbox to show the current problem sandbox
Sandbox code:
import "./styles.css";
import { useState, useEffect, useRef } from "react";
export default function App() {
//Our state holding data
const [state, setState] = useState(false);
//Our state holding the view
const [view, setView] = useState(<div></div>);
const printState = useRef(null);
//Component did mount hook
useEffect(() => {
reGenerate();
}, []);
//state update hook
useEffect(() => {
printState.current();
}, [state]);
//function to flip the state
const flipState = () => {
setState(!state);
};
//The method that updates the view
//(The idea being that I don't want to update the view on every state change)
const reGenerate = () => {
setView(
<>
<p>
State: {state && "true"} {state || "false"}
</p>
<Child callback={printState} />
</>
);
};
//Method for validation
printState.current = () => {
console.log("Printed state: " + state);
};
return (
<div className="App">
<h1>Parent-child-prop-problem (prop-lem)</h1>
<ol>
<li>click "force regeneration"</li>
<li>
click "flip state" and the value of state after the flip will be
printed in console, but it won't show up on the HTML element
</li>
<li>
Click "print state (from child)" and observe that the console is
printing the old version of the state
</li>
</ol>
<button onClick={flipState}>Flip State</button>
<button onClick={reGenerate}>Force Regeneration</button>
{view}
</div>
);
}
function Child(props) {
return (
<div>
<button onClick={props.callback.current}>Print State (from child)</button>
</div>
);
}
Taking a quick peek at your sandbox code and I see that you are storing JSX in state, which is anti-pattern and often leads to stale enclosures like you describe.
I don't want to re-create the view object every time any state changes
"Recreating" the view is a necessary step in rendering UI in React as a result of state or props updating. State should only ever store data and the UI should be rendered from the state. In other words, treat your UI like a function of state and props. Toggle the state state value and render the UI from state.
Example:
export default function App() {
//Our state holding data
const [state, setState] = useState(false);
const printState = useRef(null);
//state update hook
useEffect(() => {
printState.current();
}, [state]);
//function to flip the state
const flipState = () => {
setState(!state);
};
//Method for validation
printState.current = () => {
console.log("Printed state: " + state);
};
return (
<div className="App">
<h1>Parent-child-prop-problem (prop-lem)</h1>
<ol>
<li>
click "flip state" and the value of state after the flip will be
printed in console, but it won't show up on the HTML element
</li>
<li>
Click "print state (from child)" and observe that the console is
printing the old version of the state
</li>
</ol>
<button onClick={flipState}>Flip State</button>
<p>State: {state ? "true" : "false"}</p>
<Child callback={printState} />
</div>
);
}
function Child(props) {
return (
<div>
<button onClick={props.callback.current}>Print State (from child)</button>
</div>
);
}
It's also generally considered anti-pattern to use any sort of "forceUpdate" function and is a code smell. In almost all circumstances if you hit a point where you need to force React to rerender you are doing something incorrect. This is the time you step back and trace through your code to find where a certain piece of state or some prop isn't updated correctly to trigger a rerender naturally.
import React,{ useEffect, useState } from "react";
export default function Test() {
const [myState, setmyState] = useState(0);
return (
<div>
<h2>JavaScript Lifecycle</h2>
<button type="button" onClick={()=>{
const val = myState+1;
setmyState(val);
console.log(myState);
}}>Click This</button>
<p>value:{myState}</p>
</div>
);
}
When the button is clicked, the correct value of the state is written (near to value), but the old value is visible in the console. Sorting is correct, but when you need to send it to an api, it will go old. What can be done here? (I want to see the current value on the console)
State has not been updated before console.log has been run. If you want to see new value, use console.log(val).
Here setmyState() is an async function and the console.log is printing the value before the state has been set. To see the correct value in console.log you can use a callback.
refer to : https://www.robinwieruch.de/react-usestate-callback to get a better idea.
I' working on react since few months. I started Hooks since few days (I know quite late) the thing is, compare to react component the life cycle methodes it's look like they are different on some points.
The useEffect hook can reproduce :
-componentDidMount();
-componentDidUpdate();
-componentWillUnMount();
But I observe a difference between react's component and function it's about the way how function is unmounted. I noted the unmount methode, compare to the react's component,the react's function unmount the parent before the child/ren
import React, { ReactElement, useEffect, useState } from "react";
import { useLocation, useHistory } from "react-router-dom";
export function Child2({
count,
childrenUnmounted,
}: {
count: number;
childrenUnmounted: Function;
}): ReactElement {
useEffect(() => {
return () => {
console.log("Unmounted");
childrenUnmounted(count);
};
}, [, count]);
return (
<div>
<h2>Unmouted</h2>
</div>
);
}
export function Child1({ count }: { count: number }): ReactElement {
const [validation, setValidation] = useState(false);
const usehistory = useHistory();
const childrenUnmounted = (count: number) => {
console.log("validation", validation, count);
setValidation(false);
};
const changeUrl = () => {
setValidation(true);
usehistory.push("http://localhost:3000/${count}");
};
return (
<div>
<h2>incremente</h2>
<Child2
count={count}
childrenUnmounted={(count: number) => childrenUnmounted(count)}
/>
<button className="button" onClick={() => changeUrl()}>
validation
</button>
<button
className="button"
onClick={() => usehistory.push(`http://localhost:3000/${count}`)}
>
nope
</button>
</div>
);
}
export default function Parent(): ReactElement {
const [count, setcount] = useState(-1);
const location = useLocation();
useEffect(() => {
setcount(count + 1);
}, [, location]);
return (
<div>
<h2>hello</h2>
<h3>{count}</h3>
<Child1 count={count} />
</div>
);
}
With the code above something annoying happen, when you clicked on the validation button. Value in the Child1is at true, at the moment of the click, and it's change the URL to trigger a rerender of the Parent to change the data (here count).
The thing I don't understand is why at the unmount of the Child2, at the childrenUnmounted(count) called (to trigger the same function but in the Child1) in the Child1 the validation is equal to false even the validation was clicked ? and when you click on nope just after validation you got true... it's look like the Child1 do not matter of the current state of the validation (he use the previous state)
Someone could help me to understand what's going on ?
Thx of the help.
SOLUTION:
I used useRef instead of useState from the validation to don't depend of the re-render as Giovanni Esposito said :
because hooks are async and you could not get the last value setted for state
So useRef was my solution
Ciao, I think you problem is related on when you logs validation value. I explain better.
Your parent relationship are: Parent -> Child1 -> Child2. Ok.
Now you click validation button on Child2. validation button calls changeUrl that calls usehistory.push("http://localhost:3000/${count}"); and starts to change validation value (why starts? because setValidation is async).
If the unmounting of Child2 comes now, could be that validation value is no yet setted by async setValidation (and log returns the old value for validation).
Well, at some point this setValidation finished and sets validation to true. Now you click nope button and you get true for validation (the last value setted).
So, to make the story short, I think that what you are seeing in logs it's just because hooks are async and you could not get the last value setted for state (if you use log in this way). The only way you have to log always the last value setted is useEffect hook with value you want to log in deps list.
Because this component changes frequently, I decided to use useRef to store values (for example, a counter). On an event (such as onclick), the log shows that the number did increment by 1 each time the button was clicked, but the value isn't updated on the screen (inside the div). It's showing 0. What am I missing here?
Intended output: on click button, add() increases counter, and display value in <div>.
const counter = useRef(0);
function add() {
counter.current += 1;
console.log(counter.current); // this shows that the number did increment
}
return (
<div>
<div>{counter.current}</div> {/* this shows '0' */}
<button onClick={() => add()}>Add</button>
</div>
);
As you stated in the comments:
I am interacting with this variable in third party library listener functions. These libraries loads on page load, and receiving events from javascript listener.
Means that you want to render component on 3rd party reference change, usually you mock a render like so:
const reducer = p => !p;
const App = () => {
const counter = useRef(0);
const [, render] = useReducer(reducer, false);
function add() {
// Some 3rd party ref
counter.current += 1;
// Render and update the UI
render();
}
return (
<div>
<div>{counter.current}</div>
<button onClick={() => add()}>Add</button>
</div>
);
};
read the doc
Keep in mind that useRef doesn’t notify you when its content changes. Mutating the .current property doesn’t cause a re-render. If you want to run some code when React attaches or detaches a ref to a DOM node, you may want to use a callback ref instead.
using the useState is the best thing in our case