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.
Related
why does the first one executes at last while the other one is working fine!
I used the debugger to find out where the problem was and I noticed that when the code reached the setDummy line it went to execute the rest of the component then came back to set the state variable which was set to empty string!
I would like to know why this behaves like that
I was expecting it to set the state variable with no difference between the 2 ways of set state!
code example
import React, { useState, useRef } from 'react';
export function App(props) {
const [dummy,setDummy]=useState("First Value");
const inputRef = useRef()
const onClickHandler = () => {
// Executes at last
setDummy (()=> inputRef.current.value)
// but this executes as expected
//setDummy(inputRef.current.value)
inputRef.current.value = "";
}
return (
<div className='App'>
<input ref={inputRef} />
<button onClick={onClickHandler}>OK</button>
<p>The variable now is: {dummy}</p>
</div>
);
}
Hi maybe it should be related to the fact that as is designed in your app, setDummy is defined as a simple setter that accept as its parameter a string, not an anonymous function giving a string...I don't know if I've been clear.
I am learning react hooks, I have created below simple increment and decrement counter app.
What I want is that whenever the user clicks on the Increment or Decrement Button it should show the value in the alert Box.
After writing useEffect when the app launches I am getting an alert before clicking the Increment and Decrement Button.
One approach I know is that I can add the condition in useEffect when the value of the counter is greater than zero(alert>0), but that's not what I am looking for, I want some generic solution.
Alert should call only when the user Clicks on Increment and Decrement Button and we should use hooks.
const Counter = () =>{
const [value, setValue]= useState(0)
useEffect(()=>{
displayAlert()
},[displayAlert])
function displayAlert(){
alert(value)
}
return(
<>
<p>Counter : {value}</p>
<input type="button" value="Increment" onClick={()=>setValue(value+1)}/>
<input type="button" value="Decrement" onClick={()=>setValue(value-1)}/>
</>
)
}
export default Counter
Even if you put value as a dependency for useEffect, it will show the alert the first time you load the page (when value gets its initial value of 0). I propose creating a function that changes value and displays the alert, and calling that from onClick:
function changeAndDisplayValue(newValue){
setValue(newValue);
alert(newValue);
}
You call alert(newValue) and not alert(value) because setValue() works asynchronously, so it may not yet have finished when alert is called (therefore value may still have the old value).
Then your buttons are like this:
<input type="button" value="Increment" onClick={()=>changeAndDisplayValue(value+1)}/>
<input type="button" value="Decrement" onClick={()=>changeAndDisplayValue(value-1)}/>
There's react-use library that does provide a use "update" hook. https://github.com/streamich/react-use/blob/master/docs/useUpdateEffect.md
Check out their implementation:
https://github.com/streamich/react-use/blob/master/src/useUpdateEffect.ts
https://github.com/streamich/react-use/blob/master/src/useFirstMountState.ts
Apart from conditional check in useEffect there is no way to prevent first call of useEffect. One other approach would be to have a separate useEffect and a state which would hold the didMount property. Initially didMount would be false and as soon as the useEffect runs it would convert didMount to true. Then on your original useEffect you would check if didMount is true, and only then call your function (alert)
I believe MORĂˆ means this:
const Counter = () =>{
const [value, setValue]= useState(0)
useEffect(()=>{
displayAlert()
},[value])
function displayAlert(){
alert(value)
}
return(
<>
<p>Counter : {value}</p>
<input type="button" value="Increment" onClick={()=>setValue(value+1)}/>
<input type="button" value="Decrement" onClick={()=>setValue(value-1)}/>
</>
)
}
export default Counter
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.
I am struggling to understand why my code is not working. I am using the useEffect() hook to make a call to an API, and then I am using setState() to update my component state. In my JSX, I am mapping my info array to render the data.
Here's my code:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import './App.css';
function App() {
const [info, setInfo] = useState();
console.log(info);
useEffect(() => {
const getUsers = async () => {
const res = await axios('https://api.mocki.io/v1/b043df5a');
console.log(res.data);
setInfo(res.data);
};
getUsers();
}, []);
return (
<div>
<input type='text' placeholder='Search users' />
<input type='text' placeholder='Search users' />
{info.map((el, index) => {
console.log(el);
return <h1 key={index}>{el.city}</h1>;
})}
</div>
);
}
export default App;
However, I get this error: 'TypeError: Cannot read property 'map' of undefined'. My best guess is that my JSX is rendered before my state is populated with API info.
The code does work when I write the following JSX, which allows me to check if 'info' is true:
{info && info.map((el, index) => {
console.log(el);
return <h1 key={index}>{el.city}</h1>;
})}
Is this a normal behavior? Why is useEffect not populating my state before my page is rendered?
I would appreciate your help, as I am struggling to find the solution to this specific issue.
Thanks!
Just do this:
const [info, setInfo] = useState([]);
The issue is that you have no intial value and therefore it automatically defaults to undefined. Now you are trying to call .map on a value that is undefined therefore it throws an error. With an empty array as the initial value however, .map will loop over an empty array on the first render (before the useEffect) and it won't throw any error.
that's because useEffect hook will run after the dom render phase finished and one more thing that can cause the delay of getting data is the fact that you're calling an asynchronous function which usually get some time to finished.
so what are the possible options here:
just use empty array [] as default value
check the length of the state like info.length && whatever...
sometimes you can use the useLayoutEffect which is kinda a synchronous operation. but in your case which is an api calls solution 1 and 2 is the answer
You are trying to iterate over undefined it's normal cause you need to check first before data will come to your state. You can achieve this in two ways:
info && info.map
But your initial state must be falsy value like:
useState(null)
Or better way to set initial state to empty array and map will run when data will come and you will get no error.
So basicly useEffect function is equivalent of componentDidMount. What I'm trying to say your component renders and then it executes function passed in useEffect. To avoid this eighter use check that you introduced as a fix yourself or pass default value to useState method. I would suggest your option but with condition if info exists show it and if it's not then show some kind of loading indicator.
A use Effect with a [] as second can be interpreted as a 'componentDidMount'
To give a very simple answer, your code is 'executed' the first time without the useEffect. So indeed, 'info' will not exist. At this point your 'info' variable is not yet defined. Then, when your component 'is mounted', it will execute the code in your useEffect. Only then info will be filled in.
I would recommend to go through this documentation to fully understand this: https://reactjs.org/docs/state-and-lifecycle.html
I've built several modals as React functional components. They were shown/hidden via an isModalOpen boolean property in the modal's associated Context. This has worked great.
Now, for various reasons, a colleague needs me to refactor this code and instead control the visibility of the modal at one level higher. Here's some sample code:
import React, { useState } from 'react';
import Button from 'react-bootstrap/Button';
import { UsersProvider } from '../../../contexts/UsersContext';
import AddUsers from './AddUsers';
const AddUsersLauncher = () => {
const [showModal, setShowModal] = useState(false);
return (
<div>
<UsersProvider>
<Button onClick={() => setShowModal(true)}>Add Users</Button>
{showModal && <AddUsers />}
</UsersProvider>
</div>
);
};
export default AddUsersLauncher;
This all works great initially. A button is rendered and when that button is pressed then the modal is shown.
The problem lies with how to hide it. Before I was just setting isModalOpen to false in the reducer.
When I had a quick conversation with my colleague earlier today, he said that the code above would work and I wouldn't have to pass anything into AddUsers. I'm thinking though that I need to pass the setShowModal function into the component as it could then be called to hide the modal.
But I'm open to the possibility that I'm not seeing a much simpler way to do this. Might there be?
To call something on unmount you can use useEffect. Whatever you return in the useEffect, that will be called on unmount. For example, in your case
const AddUsersLauncher = () => {
const [showModal, setShowModal] = useState(false);
useEffect(() => {
return () => {
// Your code you want to run on unmount.
};
}, []);
return (
<div>
<UsersProvider>
<Button onClick={() => setShowModal(true)}>Add Users</Button>
{showModal && <AddUsers />}
</UsersProvider>
</div>
);
};
Second argument of the useEffect accepts an array, which diff the value of elements to check whether to call useEffect again. Here, I passed empty array [], so, it will call useEffect only once.
If you have passed something else, lets say, showModal in the array, then whenever showModal value will change, useEffect will call, and will call the returned function if specified.
If you want to leave showModal as state variable in AddUsersLauncher and change it from within AddUsers, then yes, you have to pass the reference of setShowModal to AddUsers. State management in React can become messy in two-way data flows, so I would advise you to have a look at Redux for storing and changing state shared by multiple components