Consider I got a component called Test
import {useEffect, useState} from "react";
const Test = (props) => {
const [Amount, setAmount] = useState(1);
useEffect(()=>{
if(props.defaultAmount){
setAmount(props.defaultAmount)
}
props.getResult(Amount);
},[props, Amount])
return (
<>
<span>Amount is: {Amount}</span>
<input value={Amount} onChange={(e)=>setAmount(e.target.value)}/>
</>
)
}
export default Test;
I use this in two different components (actually my pages), one with defaultAmount another without.
Page 1:
<Test getResult={getAmountResult} defaultAmount={25}/>
But this not update result and it back to default one!
Page 2:
<Test getResult={getAmountResult} />
it works fine!
Working Demo
Is there any solution to avoid this?
try to change your code like this
import {useEffect, useState} from "react";
const Test = (props) => {
const [Amount, setAmount] = useState(1);
useEffect(() => {
props.getResult(Amount);
}, [Amount])
useEffect(()=>{
if(props.defaultAmount){
setAmount(props.defaultAmount)
}
},[props.defaultAmount])
return (
<>
<span>Amount is: {Amount}</span>
<input value={Amount} onChange={(e)=>setAmount(e.target.value)}/>
</>
)
}
export default Test;
in your current implementation you always overwrite the amount state with the default
Your useEffect function is the culprit. You're setting the Amount back to defaultAmount everytime Amount changes, thus overriding the user input.
Try updating the condition within useEffect before you set the value, to make sure you don't override the user input, something like:
useEffect(()=>{
if(props.defaultAmount && Amount === 1){ // Checking if the amount is still the initial value
setAmount(props.defaultAmount)
}
props.getResult(Amount);
},[props, Amount])
When input changes, setAmount called, it will update amount and trigger useEffect hook which will set amount to default value. Try this
import { useEffect, useState } from "react";
const Test = (props) => {
const [amount, setAmount] = useState(props.defaultAmount);
useEffect(() => {
if (amount) {
props.getResult(amount);
}
}, [amount, props]);
return (
<>
<span>Amount is: {amount}</span>
<input value={amount} onChange={(e) => setAmount(e.target.value)} />
</>
);
};
export default Test;
Related
In the following code; as per my knowledege about useCallback hook 'rerendering has happened!' message should be displayed only when i am typing something inside the input field and shouldn't be printed when I am clicking on the Toggle button. But the message does print for the 2nd case. Why?
my App.js file
import {useState, useCallback} from 'react'
import List from './List'
function App() {
const [a, seta] = useState('')
const [Color, setColor] = useState("#12ab34")
const message = useCallback(() => {
console.log('rerendering has happened!')
return [a+'SAMSUNG', a+'MOTO', a+'NOKIA']
}, [a])
return (
<div className="App">
<input value={a} onChange={e => seta(e.target.value)} />
<button onClick={() => Color === "#12ab34" ? setColor("#abb111") : setColor("#12ab34") }>toggle</button>
<List message={message()} Color={Color}/>
</div>
);
}
export default App;
my List.js file
import React from 'react'
export default function List({message, Color}){
return (<>
<ul>
<li style={{color: Color}}>{message[0]}</li>
<li style={{color: Color}}>{message[1]}</li>
<li style={{color: Color}}>{message[2]}</li>
</ul>
</>)
}
Your function is not created multiple times don't worry. The log statement
'rerendering has happened!'
is running multiple times. It is because your function message() runs multiple times.
Look at <List message={message()} Color={Color}/>.
What you want to do is memoize the value returned from your function, and do not call it unnecessarily.
import { useState, useCallback, useMemo } from "react";
import List from "./List";
function App() {
const [a, seta] = useState("");
const [Color, setColor] = useState("#12ab34");
console.log({ a });
const message = useCallback(() => {
console.log("rerendering has happened!");
return [a + "SAMSUNG", a + "MOTO", a + "NOKIA"];
}, [a]);
const val = useMemo(() => message(), [message]);
return (
<div className="App">
<input value={a} onChange={(e) => seta(e.target.value)} />
<button
onClick={() =>
Color === "#12ab34" ? setColor("#abb111") : setColor("#12ab34")
}
>
toggle
</button>
<List message={val} Color={Color} />
</div>
);
}
export default App;
Link
That being said, the above approach is still counter intuitive to me. The purpose of your function is to calculate a value,. And in your <List... statement, you simple call the function to get the value. So basically you do not need the function anywhere, and just the value. Using useMemo will do the job here.
import { useState, useCallback, useMemo } from "react";
import List from "./List";
function App() {
const [a, seta] = useState("");
const [Color, setColor] = useState("#12ab34");
console.log({ a });
const message = useMemo(() => {
console.log("rerendering has happened!");
return [a + "SAMSUNG", a + "MOTO", a + "NOKIA"];
}, [a]);
return (
<div className="App">
<input value={a} onChange={(e) => seta(e.target.value)} />
<button
onClick={() =>
Color === "#12ab34" ? setColor("#abb111") : setColor("#12ab34")
}
>
toggle
</button>
<List message={message} Color={Color} />
</div>
);
}
export default App;
Link
When you use UseCallback the function will be called on each render the difference is a memorized version of it will be called (new instance will NOT be created).
It is useful when you're passing functions as props.
In your case, you should consider using UseMemo instead which returns a memorized value.
This question already has answers here:
Update variable and use it immediately React
(2 answers)
Closed 11 months ago.
I am a beginner. I am learning react js. I am having an problem. setState is always one step behind.
Here is a sample:
Here, when I typed i then the console is showing nothing. Next, when I typed the m it shows i and as it is one step behind.
I have created two functions named handleChange and handleKeyword. The functions are behaving the same. I searched on the internet and got useEffect() suggestion to solve the problem but that has not solved my problem or I can't properly implement it.
Here is my codes:
Home.jsx
import React, { useState, useEffect } from 'react';
import Search from '../../components/searchBar/Search';
import './home.scss';
const Home = () => {
const [search, setSearch] = useState('');
const [keyword, setKeyword] = useState('');
const handleChange = event => {
setSearch(event.target.value);
console.log('Search: ', search);
};
const handleKeyword = () => {
setKeyword(search);
console.log('Keyword:', keyword);
};
return (
<div className="container pb-5">
<Search
handleChange={handleChange}
handleKeyword={handleKeyword}
keyword={keyword}
/>
</div>
);
};
export default Home;
Search.jsx
import React from 'react';
import './search.scss'
const Search = props => {
return (
<div className="d-flex input-group justify-content-center">
<input
type="text"
className="form-control searchBox"
placeholder="Search for copyright free images & videos..."
value={props.value}
onChange={event => props.handleChange(event)}
/>
<button className="btn btn-primary" onClick={() => props.handleKeyword()}>
Search
</button>
</div>
);
};
export default Search;
How can I solve the problem?
In Home.jsx, you can move the console statments inside useEffect with states search and keyword as dependencies to get the updated values. This issue is because react is declarative in nature so it decides when to setState runs. It can even be batched together for performance optimisations. So useEffect can be used in such cases to listen to change in states.
import React, { useState, useEffect } from 'react';
import Search from '../../components/searchBar/Search';
import './home.scss';
const Home = () => {
const [search, setSearch] = useState('');
const [keyword, setKeyword] = useState('');
useEffect(() => {
console.log('Search: ', search);
console.log('Keyword:', keyword);
}, [search, keyword])
const handleChange = event => {
setSearch(event.target.value);
};
const handleKeyword = () => {
setKeyword(search);
};
return (
<div className="container pb-5">
<Search
handleChange={handleChange}
handleKeyword={handleKeyword}
keyword={keyword}
/>
</div>
);
};
export default Home;
The problem is setState just promise you that value will be updated It does not affect your code, just move console.logs outside handleClicks
So, when you set a new state and you will see a new value only after rerender component.
const handleKeyword = () => {
setKeyword(search);
console.log("Keyword:", keyword);
};
console.log("Keyword:2", keyword);
console.log("Keyword:", keyword); will be called in the first render with the old value
console.log("Keyword:2", keyword); will be called in the second render with a new value.
setState is async so changes to the state are not applied immediately.
see here https://reactjs.org/docs/react-component.html#setstate
I'm trying to make an option in jsx to be populated by the values in an array (currencyOptions). I used this approach but it is not working as the options still remain to be blank. The array is passed down to the component as a prop. I set the array using usestate and the data is gotten from an API. Please help.
import React from "react";
function Currencyrow(props) {
const {
currencyOptions,
selectedCurrency,
onChangeCurrency,
amount,
onChangeAmount,
} = props;
// console.log(currencyOptions);
return (
<>
<input
type="number"
className="input"
value={amount}
onChange={onChangeAmount}
></input>
<select value={selectedCurrency} onChange={onChangeCurrency}>
{currencyOptions.map((option) => {
<option key={option} value={option}>
{option}
</option>;
})}
</select>
</>
);
}
export default Currencyrow;
That is the component where I pass down currencyOptions as a prop from my main app.js
import "./App.css";
import React from "react";
import Currencyrow from "./Components/Currencyrow";
import { useEffect, useState } from "react";
const BASE_URL =
"http://api.exchangeratesapi.io/v1/latest?access_key=1fe1e64c5a8434974e17b04a023e9348";
function App() {
const [currencyOptions, setCurrencyOptions] = useState([]);
const [fromCurrency, setFromCurrency] = useState();
const [toCurrency, setToCurrency] = useState();
const [exchangeRate, setExchangeRate] = useState();
const [amount, setAmount] = useState(1);
const [amountInFromCurrency, setAmountInFromCurrency] = useState(true);
let toAmount, fromAmount;
if (amountInFromCurrency) {
fromAmount = amount;
toAmount = fromAmount * exchangeRate;
} else {
toAmount = amount;
fromAmount = amount / exchangeRate;
}
useEffect(() => {
fetch(BASE_URL)
.then((res) => res.json())
.then((data) => {
const firstCurrency = Object.keys(data.rates)[0];
setCurrencyOptions([Object.keys(data.rates)]);
setFromCurrency(data.base);
// console.log(currencyOptions);
setToCurrency(firstCurrency);
setExchangeRate(data.rates[firstCurrency]);
});
}, []);
function handleFromAmountChange() {
// setAmount(e.target.value);
setAmountInFromCurrency(true);
}
function handleToAmountChange() {
// setAmount(e.target.value);
setAmountInFromCurrency(false);
}
return (
<>
<h1>Convert</h1>
<Currencyrow
currencyOptions={currencyOptions}
selectedCurrency={fromCurrency}
onChangeCurrency={(e) => {
setFromCurrency(e.target.value);
}}
amount={fromAmount}
onChangeAmount={handleFromAmountChange}
/>
<div className="equals">=</div>
<Currencyrow
currencyOptions={currencyOptions}
selectedCurrency={toCurrency}
onChangeCurrency={(e) => {
setToCurrency(e.target.value);
}}
amount={toAmount}
onChangeAmount={handleToAmountChange}
/>
</>
);
}
export default App;
When I run the app the option element is still blank.
Is there a way to populate option html tag with an array in react?
This is possible. Just as a tip, you can always try hardcoding currencyOptions in your CurrencyRow and test it out.
Looking through your code, firstly it may be not what you want wrapping Object.keys() in an additional array in setCurrencyOptions([Object.keys(data.rates)]). Object.keys() already returns an array. You probably are not accessing the actual options in your currencyOptions.map((option) => ..). Try setting the keys array directly like this setCurrencyOptions(Object.keys(data.rates)).
Secondly, you should return the desired value inside map by either using it as an arrow function or adding the return keyword in front of the option JSX.
Other than that, is there any error displayed in the browser console? And it would certainly help you to log the mapped option to the console and see what you are actually getting from it.
Your map function should return a value.
<select>{numbers.map((m)=>{return(<option>{m}</option>)})}</select>
import React, { useState } from "react";
import Child from "./Child";
import "./styles.css";
export default function App() {
let [state, setState] = useState({
value: ""
});
let handleChange = input => {
setState(prevValue => {
return { value: input };
});
console.log(state.value);
};
return (
<div className="App">
<h1>{state.value}</h1>
<Child handleChange={handleChange} value={state.value} />
</div>
);
}
import React from "react";
function Child(props) {
return (
<input
type="text"
placeholder="type..."
onChange={e => {
let newValue = e.target.value;
props.handleChange(newValue);
}}
value={props.value}
/>
);
}
export default Child;
Here I am passing the data from the input field to the parent component. However, while displaying it on the page with the h1 tag, I am able to see the latest state. But while using console.log() the output is the previous state. How do I solve this in the functional React component?
React state updates are asynchronous, i.e. queued up for the next render, so the log is displaying the state value from the current render cycle. You can use an effect to log the value when it updates. This way you log the same state.value as is being rendered, in the same render cycle.
export default function App() {
const [state, setState] = useState({
value: ""
});
useEffect(() => {
console.log(state.value);
}, [state.value]);
let handleChange = input => {
setState(prevValue => {
return { value: input };
});
};
return (
<div className="App">
<h1>{state.value}</h1>
<Child handleChange={handleChange} value={state.value} />
</div>
);
}
Two solution for you:
- use input value in the handleChange function
let handleChange = input => {
setState(prevValue => {
return { value: input };
});
console.log(state.value);
};
use a useEffect on the state
useEffect(()=>{
console.log(state.value)
},[state])
Maybe it is helpful for others I found this way...
I want all updated projects in my state as soon as I added them
so that I use use effect hook like this.
useEffect(() => {
[temp_variable] = projects //projects get from useSelector
let newFormValues = {...data}; //data from useState
newFormValues.Projects = pro; //update my data object
setData(newFormValues); //set data using useState
},[projects])
I am having an issue with my app in that it re renders a new joke twice when I click the new button function. Here is my code:
import React, { useState, useEffect } from "react";
import { Typography, Button } from "#material-ui/core";
import Navigation from "../Navigation";
export default function RandomJoke() {
const [isLoaded, setLoaded] = useState(false);
const [jokeData, setJokeData] = useState({});
const [loadNewJoke, setLoadNewJoke] = useState(false);
function useFetch() {
async function fetchMyAPI() {
let response = await fetch("https://icanhazdadjoke.com/slack");
response = await response.json();
setJokeData(response);
setLoaded(true);
}
useEffect(() => {
fetchMyAPI();
if (loadNewJoke) setLoadNewJoke(false);
}, [loadNewJoke]);
}
useFetch();
function reloadJoke() {
setLoaded(false);
setLoadNewJoke(true);
}
return (
<>
<Navigation mainpage="RandomJoke" />
<Typography variant="h6">Random Dad Joke</Typography>
{isLoaded && <div>{jokeData.attachments[0].text}</div>}
{!isLoaded && <div>loading...</div>}
{isLoaded && (
<Button variant="contained" onClick={() => reloadJoke()}>
New one
</Button>
)}
</>
);
}
I tried adding a newjoke state hook but still couldn't work it out. Thank you
That useEffect fires whenever the value of loadNewJoke changes, right? Not just when loadNewJoke is set to true. Look closely at the calls made after a button press, and how many times setLoadNewJoke is called.
Try to move:
if (loadNewJoke) setLoadNewJoke(false);
In your fetchMyApi function. I'm guessing when you hit the button, you trigger the effect cuz u change you deps value in this case to true. Then before effect is over you change it again to false which will triggeer re-run on your effect.
But why you dont just trigger fetchApi in your callback on button click, this way you can remove 1 state [loadNewJoke, setLoadNewJoke], will also remove the useEffect and make code cleaner over all
You're using useEffect wrong, i suggest u take a look at the Rules of Hooks
Don’t call Hooks inside loops, conditions, or nested functions.
I followed what Andom Miltev said about triggering the async function directly in my callback and it now works smoothly - thank you everyone for the help :)
import React, { useState, useEffect } from "react";
import { Typography, Button } from "#material-ui/core";
import Navigation from "../Navigation";
export default function RandomJoke() {
const [isLoaded, setLoaded] = useState(false);
const [jokeData, setJokeData] = useState({});
async function fetchMyAPI() {
setLoaded(false);
let response = await fetch("https://icanhazdadjoke.com/slack");
response = await response.json();
setJokeData(response);
setLoaded(true);
console.log("fired 1");
}
useEffect(() => {
fetchMyAPI();
}, []);
return (
<>
<Navigation mainpage="RandomJoke" />
<Typography variant="h6">Random Dad Joke</Typography>
{isLoaded && <div>{jokeData.attachments[0].text}</div>}
{!isLoaded && <div>loading...</div>}
{isLoaded && (
<Button variant="contained" onClick={() => fetchMyAPI()}>
New one
</Button>
)}
</>
);
}