Why "Cards" still doesn't receive the passed value from selectedCountryInfo
I just tried passing await to the variable, still doesn't work. "Cards" still don't receive value.
<----solution: when there are have 2 setStates, should use 2 variables, not use 1 variable.(I guess if there are 3 setStates use 3 variables and so on)
I've been thinking about it for over 12 hours and can't think of a solution.
Because the default value of useState cannot put async/await.
(fetchedCountries is array,selectedCountryInfo is object)
const App = () => {
const [fetchedCountries, setFetchedCountries] = useState([]);
const [selectedCountryInfo, SetSelectedCountryInfo] = useState();
useEffect(() => {
const myCountries = async () => {
const countries = await worldWideCountries();
setFetchedCountries(countries);
SetSelectedCountryInfo(fetchedCountries[0]);
};
myCountries();
}, []);
return (
<div>
<Cards selectedCountryInfo={selectedCountryInfo} />
</div>
);
Solution:(from the 3 lines)
const countries = await worldWideCountries();
setFetchedCountries(countries);
const ww = countries[0];
SetSelectedCountryInfo(ww);
You probably want to use conditional rendering
const App = () => {
const [fetchedCountries, setFetchedCountries] = useState([]);
const [selectedCountryInfo, SetSelectedCountryInfo] = useState();
useEffect(() => {
const myCountries = async () => {
setFetchedCountries(await worldWideCountries());
SetSelectedCountryInfo(fetchedCountries[0]);
};
myCountries();
}, []);
return (
<div>
{ selectedCountryInfo && <Cards selectedCountryInfo={selectedCountryInfo} /> }
</div>
);
}
Related
I am querying Firebase real time database, saving the result into state and then rendering the results.
My data is not displaying because the page is rendering before the data is had. What I don't understand is why
useEffect(() => {
for (var key in projects) {
var projectData = {
title: projects[key].title,
description: projects[key].description,
};
result.push(<Project props={projectData} />);
}
}, [projects]);
My use effect is not running once the projects state change is triggered, populating the array and triggering the conditional render line.
What am I missing here?
const [projects, setProjects] = useState();
const { user } = useUserAuth();
const result = [];
const dbRef = ref(db, `/${user.uid}/projects/`);
useEffect(() => {
onValue(dbRef, (snapshot) => {
setProjects(snapshot.val());
});
}, []);
useEffect(() => {
for (var key in projects) {
var projectData = {
title: projects[key].title,
description: projects[key].description,
};
result.push(<Project props={projectData} />);
}
}, [projects]);
return (
<>
{result.length > 0 && result}
</>
);
};
result should also be a state!
Right now at every rerender result is being set to []. So when the useEffect does kick in, the subsequent rerender would set result to [] again.
This should not be a useEffect. Effects run after rendering, but you're trying to put <Project> elements on the page, which must happen during rendering. Simply do it in the body of your component:
const [projects, setProjects] = useState();
const { user } = useUserAuth();
const dbRef = ref(db, `/${user.uid}/projects/`);
useEffect(() => {
onValue(dbRef, (snapshot) => {
setProjects(snapshot.val());
});
}, []);
const result = [];
for (var key in projects) {
var projectData = {
title: projects[key].title,
description: projects[key].description,
};
result.push(<Project props={projectData} />);
}
return (
<>
{result.length > 0 && result}
</>
);
result.push does not mutate result in place. It instead creates a copy of the array with the new value.
As a solution, you could get your current code working by hoisting result into a state variable like so:
const [result, setResult] useState([])
...
useEffect(() => {
for (var key in projects) {
...
setResult([...result, <Project props={projectData} />])
}
}, [result, projects]);
however, this solution would result in an infinite loop...
My suggestion would be to rework some of the logic and use projects to render your Project components, instead of creating a variable to encapsulate your render Components. Something like this:
const [projects, setProjects] = useState();
const { user } = useUserAuth();
const dbRef = ref(db, `/${user.uid}/projects/`);
useEffect(() => {
onValue(dbRef, (snapshot) => {
setProjects(snapshot.val());
});
}, []);
return (
<>
{projects.length > 0 && projects.map(project=>{
var projectData = {
title: projects[key].title,
description: projects[key].description,
};
return <Project props={projectData} />
})}
</>
);
};
You're component is not re-rendering since react doesn't care about your result variable being filled.
Set it up as a state like this: const [result, setResult] = useState([]);
Then use map to return each item of the array as the desire component:
{result.length > 0 && result.map((data, index) => <Project key={index} props={data} />)}
What I would like to happen is when displayBtn() is clicked for the items in localStorage to display.
In useEffect() there is localStorage.setItem("localValue", JSON.stringify(myLeads)) MyLeads is an array which holds leads const const [myLeads, setMyLeads] = useState([]); myLeads state is changed when the saveBtn() is clicked setMyLeads((prev) => [...prev, leadValue.inputVal]);
In DevTools > Applications, localStorage is being updated but when the page is refreshed localStorage is empty []. How do you make localStorage persist state after refresh? I came across this article and have applied the logic but it hasn't solved the issue. I know it is something I have done incorrectly.
import List from './components/List'
import { SaveBtn } from './components/Buttons';
function App() {
const [myLeads, setMyLeads] = useState([]);
const [leadValue, setLeadValue] = useState({
inputVal: "",
});
const [display, setDisplay] = useState(false);
const handleChange = (event) => {
const { name, value } = event.target;
setLeadValue((prev) => {
return {
...prev,
[name]: value,
};
});
};
const localStoredValue = JSON.parse(localStorage.getItem("localValue")) ;
const [localItems] = useState(localStoredValue || []);
useEffect(() => {
localStorage.setItem("localValue", JSON.stringify(myLeads));
}, [myLeads]);
const saveBtn = () => {
setMyLeads((prev) => [...prev, leadValue.inputVal]);
// setLocalItems((prevItems) => [...prevItems, leadValue.inputVal]);
setDisplay(false);
};
const displayBtn = () => {
setDisplay(true);
};
const displayLocalItems = localItems.map((item) => {
return <List key={item} val={item} />;
});
return (
<main>
<input
name="inputVal"
value={leadValue.inputVal}
type="text"
onChange={handleChange}
required
/>
<SaveBtn saveBtn={saveBtn} />
<button onClick={displayBtn}>Display Leads</button>
{display && <ul>{displayLocalItems}</ul>}
</main>
);
}
export default App;```
You've fallen into a classic React Hooks trap - because using useState() is so easy, you're actually overusing it.
If localStorage is your storage mechanism, then you don't need useState() for that AT ALL. You'll end up having a fight at some point between your two sources about what is "the right state".
All you need for your use-case is something to hold the text that feeds your controlled input component (I've called it leadText), and something to hold your display boolean:
const [leadText, setLeadText] = useState('')
const [display, setDisplay] = useState(false)
const localStoredValues = JSON.parse(window.localStorage.getItem('localValue') || '[]')
const handleChange = (event) => {
const { name, value } = event.target
setLeadText(value)
}
const saveBtn = () => {
const updatedArray = [...localStoredValues, leadText]
localStorage.setItem('localValue', JSON.stringify(updatedArray))
setDisplay(false)
}
const displayBtn = () => {
setDisplay(true)
}
const displayLocalItems = localStoredValues.map((item) => {
return <li key={item}>{item}</li>
})
return (
<main>
<input name="inputVal" value={leadText} type="text" onChange={handleChange} required />
<button onClick={saveBtn}> Save </button>
<button onClick={displayBtn}>Display Leads</button>
{display && <ul>{displayLocalItems}</ul>}
</main>
)
Here is my code:
function StockCard(props) {
const [FetchInterval, setFetchInterval] = useState(300000);
const [StockData, setStockData] = useState({});
const [TrendDirection, setTrendDirection] = useState(0);
const [Trend, setTrend] = useState(0);
const FetchData = async () =>{
const resp = await Axios.get(`http://localhost:8080/stock/getquote/${props.API}`)
setStockData(resp.data);
}
const calculateTrendDirection = () => {
if(StockData.lastPrice.currentPrice > StockData.lastPrice.previousClosePrice){
setTrendDirection(1);
} else if (StockData.lastPrice.currentPrice < StockData.lastPrice.previousClosePrice){
setTrendDirection(-1);
} else {
setTrendDirection(0);
}
}
const calculateTrend = () => {
var result = 100 * Math.abs( ( StockData.lastPrice.previousClosePrice - StockData.lastPrice.currentPrice ) / ( (StockData.lastPrice.previousClosePrice + StockData.lastPrice.currentPrice)/2 ) );
setTrend(result.toFixed(2));
}
useEffect(() => {
FetchData();
const interval = setInterval(async () => {
await FetchData();
}, FetchInterval)
return() => clearInterval(interval);
},[FetchInterval]);
useEffect(()=>{
if(StockData.lastPrice){
console.log("Trends calculated", StockData.name);
calculateTrend();
calculateTrendDirection();
}
},[StockData])
return(
<div>
<CryptoCard
currencyName={StockData.lastPrice? StockData.name : "Name"}
currencyPrice={StockData.lastPrice? `$ ${StockData.lastPrice.currentPrice}` : 0}
icon={<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/4/46/Bitcoin.svg/2000px-Bitcoin.svg.png"/>}
currencyShortName={StockData.lastPrice? StockData.symbol : "Symbol"}
trend={StockData.lastPrice? `${Trend} %` : 0}
trendDirection={StockData.lastPrice? TrendDirection : 0}
chartData={[9200, 5720, 8100, 6734, 7054, 7832, 6421, 7383, 8697, 8850]}
/>
</div>
)
}
export default StockCard;
The basic idea is. I have a backend from which I fetch data let's say every minute(this is why i need setInterval) and I have cards which are showing off the data i fetched. I have an expression so it says generic things like "Name" until the data has arrived, then it should re-render with the real data.
But this doesn't happen. It fetches all the data, I can log it out but it doesn't get updated.
And error number 2 is it says that in the useEffects i should include the functions into dependencies.
So for example in the second useEffect where I call the function calculateTrend() and calculateTrendDirection, it says I should include not only the StockData but the two functions too.
I tried #Ozgur Sar 's fix and it worked, so it turned out the problem was "timing" with my api calls
I've got the following code:
export default function App() {
const [lastMessageId, setLastMessageId] = useState(0);
const [messages, setMessages] = useState([]);
const addMessage = (body, type) => {
const newMessage = {
id: lastMessageId + 1,
type: type,
body: body,
};
setLastMessageId(newMessage.id)
setMessages([...messages, newMessage]);
console.log("point 1", messages);
return newMessage.id;
}
// remove a message with id
const removeMessage = (id) => {
const filter = messages.filter(m => m.id !== id);
console.log("point 2", filter);
setMessages(filter);
}
// add a new message and then remove it after some seconds
const addMessageWithTimer = (body, type="is-primary", seconds=5) => {
const id = addMessage(body, type);
setTimeout(() => removeMessage(id), seconds*1000);
};
return (
...
);
}
I would like to know why after I setMessages at point 1, when I do console log it doesn't appear to be updated. This turns into a weird behaviour when I call addMessageWithTimer because when it calls removeMessage then it doesn't remove correctly the messages that I expect.
Could you please explain me how to do it?
Just like setState in class-components, the update functions of useState don't immediately update state, they schedule state to be updated.
When you call setMessages it causes react to schedule a new render of App which will execute the App function again, and useState will return the new value of messages.
And if you think about it from a pure JS perspective, messages can't change: it's just a local variable, (a const one, even). Calling a non-local function can't cause a local variable's value to change, JS just doesn't work that way.
#Retsam is correct in his explanation.
I think you would get an issue if you don't use setTimeout in addMessageWithTimer. Isn't it? But for now, it is correct.
If you don't want to give a timer of 5 seconds and still want to keep it running correctly, then give a timer of 0 seconds. It would still work okay.
what weird behavior your seeing?
when I tried your code, I'm able to remove the added message after 5 sec.
import React, { useState } from "react";
import "./styles.css";
export default function App() {
let bodyText = "";
const [lastMessageId, setLastMessageId] = useState(0);
const [messages, setMessages] = useState([]);
const addMessage = (body, type) => {
if (body === "") return;
const newMessage = {
id: lastMessageId + 1,
type: type,
body: body
};
setLastMessageId(newMessage.id);
setMessages([...messages, newMessage]);
bodyText = "";
return newMessage.id;
};
// remove a message with id
const removeMessage = (id) => {
const filter = messages.filter((m) => m.id !== id);
console.log("point 2", filter);
setMessages(filter);
};
// add a new message and then remove it after some seconds
const addMessageWithTimer = (body, type = "is-primary", seconds = 5) => {
const id = addMessage(body, type);
setTimeout(() => removeMessage(id), seconds * 1000);
};
console.log("point 1", messages);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<input onChange={(e) => (bodyText = e.target.value)} />
<button onClick={(e) => addMessage(bodyText, "is-primary")}>
Add messsage
</button>
<button onClick={(e) => addMessageWithTimer(bodyText, "is-primary", 5)}>
Add temp messsage
</button>
{messages.map((message, id) => {
return (
<div key={id}>
<p>
{message.id} {message.body}
</p>
</div>
);
})}
</div>
);
}
#Retsam was very useful with his answer as I was able to understand the problem and find a proper solution.
here is the solution that I've found:
export default function App() {
const [lastMessageId, setLastMessageId] = useState(0);
const [messages, setMessages] = useState([]);
const addMessage = (body, type="is-primary") => {
const newMessage = {
id: lastMessageId + 1,
type: type,
body: body
};
setLastMessageId(newMessage.id)
setMessages([...messages, newMessage]);
return newMessage.id;
}
// delete messages after 5 seconds
useEffect(() => {
if (!messages.length) return;
const timer = setTimeout(() => {
const remainingMessages = [...messages];
remainingMessages.shift();
setMessages(remainingMessages);
}, 5*1000);
return () => clearTimeout(timer);
}, [messages]);
return (
...
);
}
I have a small issue with a really simple component that doesn't display what I want.
const UserCards = (props) => {
const [retrievedData, setRetrievedData] = useState();
useEffect(() => {
const data = [];
props.users.map((user) => {
data.push(<UserCard key={user.username} user={user} />);
});
setRetrievedData(data);
}, []);
return (
<div className={styles.userCards}>{retrievedData && retrievedData}</div>
);
};
When I refresh the page it will not display my User cards. But If I had a timeout on useEffect like this :
const UserCards = (props) => {
const [retrievedData, setRetrievedData] = useState();
useEffect(() => {
const data = [];
setTimeout(function () {
props.users.map((user) => {
data.push(<UserCard key={user.username} user={user} />);
});
setRetrievedData(data);
}, 3000);
}, []);
return (
<div className={styles.userCards}>{retrievedData && retrievedData}</div>
);
};
Everything's fine!
I thought props were usable immediately but it seems I was wrong.
I tried to add [props] at the end of useEffect to be sure my state will be updated if props changed, but nothing...
I'm sure it's nothing but I've been struggling since yesterday!
Thank you!
Just add useEffect dependency, which will call your useEffect content every time, when dependency changed:
const UserCards = (props) => {
const [retrievedData, setRetrievedData] = useState();
useEffect(() => {
const data = [];
props.users.map((user) => {
data.push(<UserCard key={user.username} user={user} />);
});
setRetrievedData(data);
}, [props]);
return (
<div className={styles.userCards}>{retrievedData && retrievedData}</div>
);
};