I recently started using useEffect hook, and I'm facing some issues. This is a simple App component that renders an input field and submit button. On hitting submit selectItem is called which in turn calls an async function getNames(). getNames function checks if there is an existing entry, if so it returns, otherwise it calls 3rd party API getNewNames() to get newNames. I tried setting the state with this newNames field, but it seems like it is undefined in first render. But after the first render it is defined. How do I make sure that I have newNames field, so that it doesn't return undefined in any renders?
const App = () => {
const [namesArr, setNamesArr] = useState([])
const [name, setName] = useState('')
useEffect (()=> {
console.log('Inside use Effect')
}, [namesArr])
const changeInput = (val) => {
setName(val)
}
const selectItem = async() => {
const returnedVal = await getNames()
// ReturnVal is empty in first render, but filled in second render.
/
}
const getNames = async() =>{
const existingNames = namesArr.find((name)=> name === name)
if(existingNames){
return 'We have an entry'
}
else{
console.log(`Names are not reloaded properly, need to re-render`)
const newNames = await getNewNames() // this is
setName((oldNames)=> [...oldNames, newNames])
return namesArr
}
}
return <div>
<input value={name} onChange={(e)=> changeInput(e.target.value)}></input>
<button onClick={()=> selectItem()}></button>
</div>
}
I think your problem is related to the setName method. In the line after setName console.log(namesArr) will be undefined. So how can we fix this?
const getNames = async() =>{
const existingNames = namesArr.find((name)=> name === name)
if(existingNames){
return 'We have an entry'
}
else{
const newNames = await getNewNames();
// We created our new list outside
// If x is a list, prepend an ellipsis ...newNames
const newList = [...namesArr, newNames];
// setName string is a state. Incorrect state updating.
// setNamesArr instead of setName
setNamesArr(newList)
return newList;
}
}
Now there are two possibilities here.
returnedVal === array or returnedVal === 'We have an entry'
const selectItem = async() => {
const returnedVal = await getNames();
console.log(returnedVal); // array or string.
}
Related
I'm getting the selected checkbox name into an array. Then, I want to use it later for some calculations. but when I try to get the values it's giving the wrong values. I want to allow users to select the ticket count by using the +/- buttons. following is my code
Code
Getting the selected seat from the checkboxes
const [seatCount, setSeatCount] =React.useState("");
const [open, setOpen] = React.useState(false);
const [counter, setCounter] = React.useState(0);
var seatsSelected = []
const handleChecked = async (e) => {
let isChecked = e.target.checked;
if(isChecked){
await seatsSelected.push(e.target.name)
} else {
seatsSelected = seatsSelected.filter((name) => e.target.name !== name);
}
console.log(seatsSelected);
}
calculations happening on a dialog
const handleClickOpen = () => { //open the dialog
setOpen(true);
console.log(seatsSelected);
const seatTotal = seatsSelected.length
console.log(seatTotal)
setSeatCount(seatTotal)
console.log(seatCount)
};
const handleClose = () => { //close the dialog
setOpen(false);
};
const handleIncrement = () =>{ //increse the count
if(counter < seatsSelected.length){
setCounter(counter + 1)
} else {
setCounter(counter)
}
}
const handleDecrement = () =>{ //decrese the count
if(counter >= seatsSelected.length){
setCounter(counter - 1)
} else {
setCounter(counter)
}
}
Setting the state in React acts like an async function.
Meaning that the when you set the state and put a console.log right after it, like in your example, the console.log function runs before the state has actually finished updating.
Which is why we have useEffect, a built-in React hook that activates a callback when one of it's dependencies have changed.
Example:
useEffect(() => {
console.log(seatCount);
}, [seatCount);
The callback will run every time the state value changes and only after it has finished changing and a render has occurred.
const [filter, setFilter] = useState([]);
let brandList = [];
const handleCheckBox = (e) => {
const { value, checked } = e.target;
if (checked) {
brandList.push(value);
} else {
let ind = brandList.indexOf(value);
brandList.splice(ind, 1);
}
console.log(brandList);
setFilter([...brandList]);
};
When I try to push an element to an array but it takes only one rest of the elements are got removed automatically. It happens when I using the useState hook.
Please checkout the image below:
brandList is redeclared an empty array each render cycle. This is why you only ever see that last filter item value added.
Use the filter state directly.
Example:
const [filter, setFilter] = useState([]);
const handleCheckBox = (e) => {
const { value, checked } = e.target;
if (checked) {
setFilter(filter => [...filter, value]);
} else {
setFilter(filter => filter.filter(el => el !== value));
}
};
I have a function which is async and returns a value.
How do I get this value when I call that function within a React function component?
For me it always shows [object Promise].
I already tried some stuff with the Effect Hook.
function Home() {
const { publicKey } = useWallet();
const [buttonClicked, setClick] = useState(false);
let tokenAccounts;
useEffect(() => {
if (buttonClicked) {
//This is the async function that I am calling
tokenAccounts = getTokenAccounts(publicKey);
console.log("token account: " + tokenAccounts);
readyToShow = true;
}
})
function handleButClick() {
setClick(true);
}
let display;
if (buttonClicked) {
display = <div>test{tokenAccounts}</div>
} else {
display = (publicKey && <button onClick={handleButClick}>click</button>);
}
return (
<div>
{display}
</div>
);
}
(It shows button and after the button is clicked it does show test and should also show the variable tokeAccounts which in my case is a [object Promise])
Putting an await in front of the function call would be my intentional solution but then it says Unexpected reserved word 'await'
Try this:
const Home = () => {
const { publicKey } = useWallet();
const [tokenAccounts, setTokenAccounts] = useState(undefined);
const handleButClick = () => {
getTokenAccounts(publicKey)
.then(result => setTokenAccounts(result));
// Depending on API may need result.data or other
}
return (
<React.Fragment>
{tokenAccounts === undefined && publicKey &&
<button onClick={handleButClick}>click</button>
}
{tokenAccounts && <div>test{tokenAccounts}</div>}
</React.Fragment>
);
}
When the component first loads the tokenAccounts state variable is undefined so your button element will be displayed. When the button is clicked the event handler calls your async action and accesses the return value via the .then statement (also check out .catch and .finally). The tokenAccounts state variable is then set and the state change causes the component to refresh. Since tokenAccounts now has a value it is displayed and the button element hidden.
function Home() {
const { publicKey } = useWallet();
const [buttonClicked, setClick] = useState(false);
let tokenAccounts;
useEffect(() => {
const getTokenAccountsLocally = async () => {
if (buttonClicked) {
tokenAccounts = await getTokenAccounts(publicKey);
console.log("token account: " + tokenAccounts);
}
}
getTokenAccountsLocally();
})
function handleButClick() {
setClick(true);
}
let display;
if (buttonClicked) {
//runs through this before tokenAccounts is loaded with the value
display = <div>this is the {tokenAccounts}</div>
} else {
display = (publicKey && <button onClick={handleButClick}>click</button>);
}
return (
<div>
{display}
</div>
);
}
Wrapping the async function call around another async function locally and then call it so I can add wait for the other async function call kind of works. tokenAccounts is now loaded with the returned value from the async function call. BUT it gets shown before it actually loaded the value.
Currently, I'm passing a value from a Child Component to a Parent in React.
const ParentComponent = () => {
const [callback, setCallback] = useState(null);
const handleCallback = (childData) => {
console.log(childData);
return childData;
};
let childDataType = handleCallback();
if (childDataType === "success") {
setCallback(true)
} else if (childDataType === "error") {
setCallback(true)
}
return (
<div dataValue={dataValue(callback)}/>
)
}
const ChildComponent = ({dataValue}) => {
let callback = thisData[index].value
return (
<div dataValue={dataValue(callback)}/>
)
}
I'm trying to use that value to set a state. If the value from the child equals a string then the state is true else false.
Right now I'm able to get the data to console.log(childData) inside handleCallback. However, the data comes back undefined at first then sets to a value. Because of this, it sets childDataType to undefined. Which in turn sets my state to undefined.
I need to have the variable childDataType to wait for the function to run and return a defined value before trying to set the callback's state. How do I get handleCallback to await a defined value before returning its initial value?
What I like to do is the following
const handleCallback = (childData) => {
return new Promise((resolve,reject)=>{
resolve(childData)
})
};
then u can do
let childDataType = await handleCallback();
This is a nice approach because if any error happens in the function u just call reject(error).
You can change the function to an async function and await for it to return the value.
const handleCallback = async (childData) => {
console.log(childData);
return childData;
};
let childDataType = await handleCallback();
Did you try to use the "await" expression?
let childDataType = await handleCallback();
Check this for more info:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await
I have an API call set up with two search buttons from one input box. Each button adds something using state to the api call which the code below should demonstrate. The calls both work fine independently and display the correct information.
If a user has clicked the 'wild cards' button the results show but then on clicking the 'standard set' button the results don't re-render the correct results until the button is pressed a second time (vice versa for both buttons).
I have removed the un-related code as to condense the question
Any help appreciated
Home.js - with api call, state and functions passed down as props to the searchBar
export const Home = () => {
const [searchTerm, setSearchTerm] = useState('')
const [wild, setWild] = useState('')
let accessToken;
const getCards = async() => {
try {
await getToken()
const response = await fetch(`https://eu.api.blizzard.com/hearthstone/cards/?collectible=1${wild}&textFilter=${searchTerm}&locale=en-US$access_token=${accessToken}`, {
headers: {
Authorization: `Bearer ${accessToken}`
}})
const data = await response.json()
const {cards} = data
if (cards){
const newCards = cards.map((card) => { ** some data i want ** }
setCards(newCards)
}
} catch (error) {
console.log(error)
}
}
return (
<SearchBar getCards = {getCards}
setWild = {setWild}
setSearchTerm = {setSearchTerm} />
</div>
</div>
)
}
SearchBar Component - again condensed for the sake of this question
export const SearchBar = (props) => {
const searchBox = useRef('')
const handleClickWild = (e) => {
e.preventDefault()
props.setWild('')
props.getCards()
}
const handleClickStandard = (e) => {
e.preventDefault()
props.setWild('&set=standard')
props.getCards()
}
const handleChange = (e) => {
props.setSearchTerm(e.target.value)
}
return (
<form>
<input className = 'input-search'
type = 'text'
placeholder = 'Search for Cards by Name...'
ref = {searchBox}
onChange = {handleChange} />
<div className = 'search-buttons'>
<input type = 'submit' className = 'button-search' onClick = {handleClickWild} value = 'Wild Cards' />
<input type = 'submit' className = 'button-search' onClick = {handleClickStandard} value = 'Standard Set' />
</div>
</form>
)
}
You have to use useEffect hook here.
You can use wild in dependency array and whenever you change the value of searchTerm use effect will automatically call your getCards function.
As you mentioned in the comment you want to show changes when user search anything then keep the wild in simple variable and add search term in useEffect and if you want you can add both in the useEffect dependency array
useEffect(()=> {
getCards()
}, [searchTerm])
Just remove explicite calls of props.getCards after setting wild from SearchBar component.
I solved this with useEffect as suggested but I added an 'if (hasSearched)' as state value of is the user had searched previously to prevent the API auto calling on page load