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
Related
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.
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'm getting stuck about how to use effects together with app logic.
Suppose this component:
import React, { useEffect, useState } from "react";
export default function App() {
const [query, setQuery] = useState('');
useEffect( () => {
fetch('https://www.google.com?q='+query)
.then(response => console.log(response))
}); // depends on what?
return (
<div>
<input onChange={e => setQuery(e.target.value)} value={query} />
<button>Ask Google about {query}</button>
</div>
);
}
I want that:
when (and only) the user clicks the button the fetch is run with the correct query value of the input
if the fetch is still in progress and the user clicks, the fetch is skipped but the effect is fired (meaning: I intentionally not disable the button, I want that the effect function is run, but I put a check inside that function not to execute the fetch).
Problems:
The effect shouldn't fire on mount (it wouldn't make any sense)
The effect shouldn't fire when the query changes, but if I don't put the query variable inside the useEffect dependency array, React complains (react-hooks/exhaustive-deps)
The effect should fire when the user click on the button; I achieved this for example using a fake state isRun, setting onClick={setIsRun(true)}, making the effect depending on [isRun], setting setIsRun(false) at the end of the effect function, and checking if (!isRun) at the beginning of the effect function to prevent that when is set to false from the effect itself it is run again since the state changes. This works, but I find it very verbose and uncomfortable...
The effect should fire if the button is clicked again (with the same query value or not) and the previous fetch has not yet finished without running the fetch: with the previous solution with isRun it wouldn't fire because isRun is already set to 1 so there is no state change; maybe with another state there is a way, but again very verbose and counterintuitive.
Most importantly: the code should be clean and readable without using "tricks"!
How would you write such a component?
It sounds like you shouldn't be using useEffect for this at all. You want this to happen on a user action, not as an effect:
when (and only) the user clicks the button the fetch is run with the correct query value of the input
Remove useEffect and create a function to handle the click:
const handleClick = (e) => {
fetch('https://www.google.com?q='+e.target.value)
.then(response => console.log(response));
};
And pass that function to the component:
<button onClick={handleClick}>Ask Google about {query}</button>
What seems confusing here are these requirements:
if the fetch is still in progress and the user clicks, the fetch is skipped but the effect is fired
The effect should fire if the button is clicked again (with the same query value or not) and the previous fetch has not yet finished without running the fetch
The only thing the function does is execute a fetch. So should that operation happen or not? Your proposed solution of keeping state in a variable (isRun) to determine if it should happen or not should work in this case. I think the problem before was mixing that up with useEffect when all you really want is a function. Add isRun to state and update it accordingly when performing the operation:
const [isRun, setIsRun] = useState(false);
const handleClick = (e) => {
if (isRun) { return; }
setIsRun(true);
fetch('https://www.google.com?q='+e.target.value)
.then(response => {
console.log(response);
setIsRun(false);
});
};
I'm not sure if you want to insist on using useEffect but it does not seem appropriate for this situation. What I could do is call a function on button click.
import React, { useEffect, useState } from "react";
export default function App() {
const [query, setQuery] = useState('');
const handleQuery = (query) => {
fetch('https://www.google.com?q='+query)
.then(response => console.log(response))
}
return (
<div>
<input onChange={e => setQuery(e.target.value)} value={query} />
<button onClick={() => handleQuery(query)}>Ask Google about {query}</button>
</div>
);
}
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.
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