I have a react component with this state
const [name, setName] = useState('')
const [comment, setComment] = useState('')
const [notes, setNotes] = useState([])
this function handles the input elements to fill the order
const handleComments = () => {
setNotes([...notes, {
name,
comment
}])
setName('')
setComment('')
}
and this function sends the info to the server
const update = async () => {
const newNotes = notes.map(note => ({
name,
comment
}))
return updateNotesPromise(newNotes)
}
here I have a button that has to execute both functions
<Button onClick={} />
How can I create a function that is passed through the onClick method and executes handleComments in order to load the info on the DOM and then, once that info there, executes the update function and saves the order info into the DB ?
It looks like you're using functional components, so you can create a useEffect that makes an API put request whenever notes gets updated:
useEffect(()=> {
updateNotesPromise(notes);
},[notes])
I'm assuming updateNotesPromise is a function that makes your request call? It's also unclear why newNotes is being mapped from notes, or why update is async when it doesn't await anything. Your onClick would simply trigger handleNotes (I'm assuming that is your submit button).
Here's a way to handle the component updating and server communicating with error handling:
const onButtonClicked = useCallback(async (name, comment) => {
// cache the olds notes
const oldNotes = [...notes];
// the updated notes
const newNotes = [...notes, {
name,
comment
}];
// update the component and assume the DB save is successful
setNotes(newNotes);
try {
// update the data to DB
await updateNotesPromise(newNotes);
} catch(ex) {
// when something went wrong, roll back the notes to the previous state
setNotes(oldNotes);
}
}, [notes]);
Related
Here is the new script with the find function which allows me to identify a single element of the array for sure but there is still a small problem. As you can see it's my const crypto which contains the data I want to display on the front end. However when I want to call crypto at the return level the const is not recognized.
Hello again,
I updated the script now it works I can display on the front end the data I want however I have the impression that the request to the api is executed several times when I would like there to be only one request
I put below a screen of the console.log of my script.
As you can see the data requested is displayed first as undefined then it is displayed several times, then I put blocked due to the too large number of requests made in little time
Thanks for your help
How do I make my requests :
import { useState, useEffect } from "react";
function App() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null)
useEffect(() => {
fetch("http://localhost:4500/coingeckotest")
.then(response => {
if (response.ok) {
return response.json()
}
throw response;
})
.then(data => {
setData(data)
})
.catch(error => {
console.error("Error fetching data: ", error)
setError(error)
})
.finally(() => {
setLoading(false)
})
}, [])
const uniswap = data&&data[0].tickers?.find(donne =>
donne.trade_url === 'https://app.uniswap.org/#/swap?
inputCurrency=0x2260fac5e5542a773aa44fbcfedf7c193bc2c599&
outputCurrency=ETH')
const uniprice = uniswap?.converted_last?.usd
const sushiswap = data&&data[1].tickers?.find(donne =>
donne.trade_url === 'https://app.sushi.com/swap?
inputCurrency=0x2260fac5e5542a773aa44fbcfedf7c193bc2c59
9&outputCurrency=ETH')
const sushiprice = sushiswap?.converted_last?.usd
const curvefinance = data&&data[2].tickers?.find(donne =>
donne.base === 'DAI')
const curveprice = curvefinance?.converted_last?.usd
const quickswap = data&&data[3].tickers?.find(donne =>
donne.trade_url === 'https://quickswap.exchange/#/swap?
inputCurrency=0x0d500b1d8e8ef31e21c99d1db9a6444d3adf127
0&outputCurrency=0xbbba073c31bf03b8acf7c28ef0738decf369
5683')
const quickprice = quickswap?.converted_last?.usd
console.log(uniprice)
console.log(sushiprice)
console.log(curveprice)
console.log(quickprice)
if (loading) return "Loading..."
if(error) return "Error !"
return (
<>
</>
)
}
export default App;
Thank you in advance for your answers
You can use the Array.find method to find out the first entry that matches a particular coin_id. The code for that would be:
const desiredCoinID = "something"; (determined by some user input)
const desiredCoinObject = data.find(coin => coin.coin_id === desiredCoinID);
const priceInUSD = desiredCoinObject?.tickers?.converted_last?.usd;
——-Updated Answer——-
Hi, this is my answer to your updated question. const crypto that you use is available only within the scope of the callback of the useEffect function, which is why you cannot see the results on your screen. There are two ways you can go about it:
First, you can declare crypto as a let outside the useEffect and then update it inside your useEffect. That way your crypto will have global scope. But there is a better way to do this, which is to use the useState hook.
You can declare a crypto, setCrypto using useState and then use the setter to update the value if crypto inside useEffect after the data fetching is over. Let me know if you need help writing code.
So suppose I have a page that displays all the user's posts. Right now when the user creates a post, it stores it into the database. But that new post will not appear unless I log out of the app and log back in. How can I make it so that the post will appear without having to logout?
const [Posts,setPosts] = useState([])
useEffect(()=>{
const fetchUserPosts = async () => {
const res = await client.get('/fetch-user-posts')
setPosts(res.data.posts)
}
fetchUserPosts()
},[])
And in the rendering I just took the Posts and map it to display its contents. I know this problem exists somewhere but I don't know what keywords to google. I have seen posts asking to use JQuery but I bet there is a simpler solution? Any pointers are appreciated.
Take the function of fetchUserPosts out of the useEffect like this:
const fetchUserPosts = async () => {
const res = await client.get('/fetch-user-posts')
setPosts(res.data.posts)
}
Now, useEffect will be like this:
useEffect(() => {
fetchUserPosts()
}, [])
Next, wherever you function of create user posts is, call fetchUserPosts() like this:
const addUserPosts = async () => {
// logic of adding new user post
if (success) {
await fetchUserPosts()
}
}
I'm currently working on a search functionality in React Native using axios.
When implementing search functionality i'm using debounce from lodash to limit the amount of requests sent.
However, since request responses are not received in same order there is a possibility of displaying incorrect search results.
For example when the user input 'Home deco' in input field there will be two requests.
One request with 'Home' and next with 'Home deco' as search query text.
If request with 'Home' takes more time to return than second request we will end up displaying results for 'Home' query text not 'Home deco'
Both results should be displayed to the user sequentially, if responses are returned in order but if 'Home' request is returned after 'Home deco' request then 'Home' response should be ignored.
Following is a example code
function Search (){
const [results, setResults] = useState([]);
const [searchText, setSearchText] = useState('');
useEffect(() => {
getSearchResultsDebounce(searchText);
}, [searchText]);
const getSearchResultsDebounce = useCallback(
_.debounce(searchText => {
getSearchResults(searchText)
}, 1000),
[]
);
function getSearchResults(searchText) {
const urlWithParams = getUrlWithParams(url, searchText);
axios.get(urlWithParams, { headers: config.headers })
.then(response => {
if (response.status === 200 && response.data)
{
setResults(response.data);
} else{
//Handle error
}
})
.catch(error => {
//Handle error
});
}
return (
<View>
<SearchComponent onTextChange={setSearchText}/>
<SearchResults results={results}/>
</View>
)
}
What is the best approach to resolve above issue?
If you want to avoid using external libraries to reduce package size, like axios-hooks, I think you would be best off using the CancelToken feature included in axios.
Using the CancelToken feature properly will also prevent any warnings from react about failing to cancel async tasks.
Axios has an excellent page explaining how to use the CancelToken feature here. I would recommend reading if you would like a better understanding of how it works and why it is useful.
Here is how I would implement the CancelToken feature in the example you gave:
OP clarified in the replies that they do not want to implement a cancelation feature, in that case I would go with a timestamp system like the following:
function Search () {
//change results to be a object with 2 properties, timestamp and value, timestamp being the time the request was issued, and value the most recent results
const [results, setResults] = useState({
timeStamp: 0,
value: [],
});
const [searchText, setSearchText] = useState('');
//create a ref which will be used to store the cancel token
const cancelToken = useRef();
//create a setSearchTextDebounced callback to debounce the search query
const setSearchTextDebounced = useCallback(
_.debounce((text) => {
setSearchText(text)
), [setSearchText]
);
//put the request inside of a useEffect hook with searchText as a dep
useEffect(() => {
//generate a timestamp at the time the request will be made
const requestTimeStamp = new Date().valueOf();
//create a new cancel token for this request, and store it inside the cancelToken ref
cancelToken.current = CancelToken.source();
//make the request
const urlWithParams = getUrlWithParams(url, searchText);
axios.get(urlWithParams, {
headers: config.headers,
//provide the cancel token in the axios request config
cancelToken: source.token
}).then(response => {
if (response.status === 200 && response.data) {
//when updating the results compare time stamps to check if this request's data is too old
setResults(currentState => {
//check if the currentState's timeStamp is newer, if so then dont update the state
if (currentState.timeStamp > requestTimeStamp) return currentState;
//if it is older then update the state
return {
timeStamp: requestTimeStamp,
value: request.data,
};
});
} else{
//Handle error
}
}).catch(error => {
//Handle error
});
//add a cleanup function which will cancel requests when the component unmounts
return () => {
if (cancelToken.current) cancelToken.current.cancel("Component Unmounted!");
};
}, [searchText]);
return (
<View>
{/* Use the setSearchTextDebounced function here instead of setSearchText. */}
<SearchComponent onTextChange={setSearchTextDebounced}/>
<SearchResults results={results.value}/>
</View>
);
}
As you can see, I also changed how the search itself gets debounced. I changed it where the searchText value itself is debounced and a useEffect hook with the search request is run when the searchText value changes. This way we can cancel previous request, run the new request, and cleanup on unmount in the same hook.
I modified my response to hopefully achieve what OP would like to happen while also including proper response cancelation on component unmount.
We can do something like this to achieve latest api response.
function search() {
...
const [timeStamp, setTimeStamp] = "";
...
function getSearchResults(searchText) {
//local variable will always have the timestamp when it was called
const reqTimeStamp = new Date().getTime();
//timestamp will update everytime the new function call has been made for searching. so will always have latest timestampe of last api call
setTimeStamp(reqTimeStamp)
axios.get(...)
.then(response => {
// so will compare reqTimeStamp with timeStamp(which is of latest api call) if matched then we have got latest api call response
if(reqTimeStamp === timeStamp) {
return result; // or do whatever you want with data
} else {
// timestamp did not match
return ;
}
})
}
}
Here's my page:
function MyAssets() {
const [assetData, setAssetData] = useState({})
const [assetArray, setAssetArray] = useState([{symbol:'', amount:''}])
and its component below.
I'm trying to fetch data in this component, I set a setInterval to run it every second.
but requests are too many, it sometimes send back status code 429 and fail.
So I add an if statement, if I fetched data successfully, I clear this setInterval .
This one failed too, it seems every time it triggers setState, the whole component render again, and my checkState become false, and triggers again.
How should I stop this component once I fetch date from API successfully?
//React component
function AssetRow(props) {
const [price, setPrice] = useState(null)
const [checkState, setCheckState] = useState(false)
const fetchStockPrice = async()=>{
const data = await api.getStock(props.item.symbol)
setPrice(data.data.latestPrice)
}
//I try to run fetchStockPrice() every 1 second here
useEffect(()=>{
const doWork = setInterval(() => {
if (checkState === false){
fetchStockPrice()
setCheckState(true)
} else if (checkState === true) {
clearInterval(doWork)
}
}, 1000)
return () => clearInterval(doWork)
}, [])
return (
<h1>{price}</h1>
)
}
I have two components, one that uploads a file, and another that is a form that can be submitted. The uploader has a callback for when the upload is complete and the form has a callback for when the form is submitted. My goal is to make a request to the backend when the uploader is done uploading and the form has been submitted, without caring which happens first.
My current solution is something like this:
const [isFileUploaded, setFileUploaded] = useState(false);
const [isFormSubmitted, setFormSubmitted] = useState(false);
const handleUploadFinish = ( {signedBlobId} ) => {
// update params to be sent to the backend with the signedBlobId
setFileUploaded(true)
if (isFormSubmitted) {
// make the backend call
}
}
const handleFormSubmitted = (values) => {
// update params to be sent to the backend with the values
setFormSubmitted(true)
if (setFileUploaded) {
// make the backend call
}
}
However, I read in the React documentation on state that setting state is an asynchronous operation. This makes me worry that it's possible that if both callbacks happen to be called at nearly exactly the same time, it's possible that both isFileUploaded and isFormSubmitted will still be false when they are checked, preventing the backend call from happening.
Is this a valid concern? If so, what is a better way of handling this?
Yes, with the way you have your logic constructed there will likely be race-conditions. You should want your code to have a more synchronous pattern. Fortunately, there is a way to resolve this by integrating the useEffect() hook. Essentially it will be triggered anytime a value you are subscribing to has changed.
In this case we want to verify that both isFileUploaded and isFormSubmitted are true, only then will we make the final backend API call.
Consider an example like this:
import React, { useState, useEffect } from "react"
const myComponent = () => {
const [isFileUploaded, setFileUploaded] = useState(false);
const [isFormSubmitted, setFormSubmitted] = useState(false);
const [params, setParams] = useState({})
const handleUploadFinish = ( {signedBlobId} ) => {
// update params to be sent to the backend with the signedBlobId
setFileUploaded(true)
}
const handleFormSubmitted = (values) => {
// update params to be sent to the backend with the values
setFormSubmitted(true)
}
useEffect(() => {
if(isFormSubmitted && isFileUploded){
...make backend call with updated params
}
}, [isFormSubmitted, isFileUploaded])
return(
....
)
}