React try again if error when fetching data from API - javascript

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>
)
}

Related

Get axios responses in the same order as requests for search functionality

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 ;
}
})
}
}

How to input realtime data into NextJS?

I am just starting to learn Next.js framework.
I need help to solve a problem that I do not understand right now. In normal Vanilla JavaScript and React I can display the resulting API in HTML using the setInterval method.
My API changes data in every 3 seconds. I want to incorporate such variable data into my Next.js app.
Below I have combined the two APIs into a single props to carry data to other components.
export async function getServerSideProps(context) {
const [twoDApiRes, saveApiRes] = await Promise.all([
fetch(_liveResult),
fetch(_localTxt),
]);
const [twoDApi, saveApi] = await Promise.all([
twoDApiRes.json(),
saveApiRes.text(),
]);
// Regex
let csv_data = saveApi.split(/\r?\n|\r/);
// Loop through
const retrieveData = csv_data.map((el) => {
let cell_data = el.split(',');
return cell_data;
});
return {
props: { twoDApi, retrieveData },
};
}
The main thing to know is that you want to change the data every three seconds in Next.js getServerSideProps.
You can use useEffect hook to refresh data every 3 seconds.
// This function will return Promise that resolves required data
async function retrieveData() {
// retrieves data from the server
}
function MyPage() {
const [data, setData] = useState();
const [refreshToken, setRefreshToken] = useState(Math.random());
useEffect(() => {
retriveData()
.then(setData)
.finally(() => {
// Update refreshToken after 3 seconds so this event will re-trigger and update the data
setTimeout(() => setRefreshToken(Math.random()), 3000);
});
}, [refreshToken]);
return <div>{data?.name}</div>
}
Or you can use react-query library with {refetchInterval: 3000} options to refetch data every 3 seconds.
Here's an example for using react-query.

How to implement this without triggering an infinite loop with useEffect

So I have a situation where I have this component that shows a user list. First time the component loads it gives a list of all users with some data. After this based on some interaction with the component I get an updated list of users with some extra attributes. The thing is that all subsequent responses only bring back the users that have these extra attributes. So what I need is to save an initial state of users that has a list of all users and on any subsequent changes keep updating/adding to this state without having to replace the whole state with the new one because I don't want to lose the list of users.
So far what I had done was that I set the state in Redux on that first render with a condition:
useEffect(() => {
if(users === undefined) {
setUsers(userDataFromApi)
}
userList = users || usersFromProp
})
The above was working fine as it always saved the users sent the first time in the a prop and always gave priority to it. Now my problem is that I'm want to add attributes to the list of those users in the state but not matter what I do, my component keeps going into an infinite loop and crashing the app. I do know the reason this is happening but not sure how to solve it. Below is what I am trying to achieve that throws me into an infinite loop.
useEffect(() => {
if(users === undefined) {
setUsers(userDataFromApi)
} else {
//Users already exist in state
const mergedUserData = userDataFromApi.map(existingUser => {
const matchedUser = userDataFromApi.find(user => user.name === existingUser.name);
if (matchedUser) {
existingUser.stats = user.stats;
}
return existingUser;
})
setUsers(mergedUserData)
}
}, [users, setUsers, userDataFromApi])
So far I have tried to wrap the code in else block in a separate function of its own and then called it from within useEffect. I have also tried to extract all that logic into a separate function and wrapped with useCallback but still no luck. Just because of all those dependencies I have to add, it keeps going into an infinite loop. One important thing to mention is that I cannot skip any dependency for useCallback or useEffect as the linter shows warnings for that. I need to keep the logs clean.
Also that setUsers is a dispatch prop. I need to keep that main user list in the Redux store.
Can someone please guide me in the right direction.
Thank you!
Since this is based on an interaction could this not be handled by the the event caused by the interaction?
const reducer = (state, action) => {
switch (action.type) {
case "setUsers":
return {
users: action.payload
};
default:
return state;
}
};
const Example = () => {
const dispatch = useDispatch();
const users = useSelector(state => state.users)
useEffect(() => {
const asyncFunc = async () => {
const apiUsers = await getUsersFromApi();
dispatch({ type: "setUsers", payload: apiUsers });
};
// Load user data from the api and store in Redux.
// Only do this on component load.
asyncFunc();
}, [dispatch]);
const onClick = async () => {
// On interaction (this case a click) get updated users.
const userDataToMerge = await getUpdatedUserData();
// merge users and assign to the store.
if (!users) {
dispatch({ type: "setUsers", payload: userDataToMerge });
return;
}
const mergedUserData = users.map(existingUser => {
const matchedUser = action.payload.find(user => user.name === existingUser.name);
if (matchedUser) {
existingUser.stats = user.stats;
}
return existingUser;
});
dispatch({ type: "setUsers", payload: mergedUserData });
}
return (
<div onClick={onClick}>
This is a placeholder
</div>
);
}
OLD ANSWER (useState)
setUsers can also take a callback function which is provided the current state value as it's first parameter: setUsers(currentValue => newValue);
You should be able to use this to avoid putting users in the dependency array of your useEffect.
Example:
useEffect(() => {
setUsers(currentUsers => {
if(currentUsers === undefined) {
return userDataFromApi;
} else {
//Users already exist in state
const mergedUserData = currentUsers.map(existingUser => {
const matchedUser = userDataFromApi.find(user => user.name === existingUser.name);
if (matchedUser) {
existingUser.stats = user.stats;
}
return existingUser;
});
return mergedUserData;
}
});
}, [setUsers, userDataFromApi]);

Cannot access state variable inside function [duplicate]

This question already has answers here:
The useState set method is not reflecting a change immediately
(15 answers)
Closed 2 years ago.
I'm building a messaging application. I'm retrieving the message log from my backend every 5 seconds with setTimeout and storing it in a state variable. I'm having it also scroll to the bottom of the chat window every timeout, but I am trying to make it only occur when there is a new message in the response object. The issue is that I cannot access the state variable inside my function that retrieves it. Ideally I would compare the response object's length to the state variable's current length to determine if there is an new response.
Here's my code:
import React from "react"
import axios from "axios"
export default function Messaging(props) {
const [messages, setMessages] = React.useState([])
const getMessages = () => {
const config = {
method: "get",
url: "localhost:3001/get-messages",
}
axios(config)
.then((response) => {
console.log(messages.length) // prints 0 in reference to the initial state value
if (response.data.get.records.length !== messages.length) {
// function to scroll to the bottom of the chat window
}
setMessages(response.data.get.records)
setTimeout(getMessages, 5000)
})
}
console.log(messages.length) // prints actual value correctly
// Initial retrieval of chat log
React.useEffect(() => {
getMessages()
}, [])
}
I'm open to better suggestions in handling the logic as well. Thanks in advance!
setMessages will update messages in the next render
With that in mind, you could create an Effect that whenever messages.length changes, scrolls to your element
import React from "react"
import axios from "axios"
export default function Messaging(props) {
const [messages, setMessages] = React.useState([])
React.useEffect(() => {
// store the id to clear it out if required
let timeoutId
const getMessages = () => {
const config = {
method: "get",
url: "localhost:3001/get-messages",
}
axios(config)
.then((response) => {
// store the messages
setMessages(response.data.get.records)
// here we tell to poll every 5 seconds
timeoutId = setTimeout(getMessages, 5000)
})
}
getMessages()
return () => {
if (timeoutId) {
clearTimeout(timeoutId)
}
}
}, [setMessages])
React.useEffect(() => {
// code here to scroll
// this would only trigger if the length of messages changes
}, [messages.length])
}
I also added the cleanup in your first effect, so when your component unmounts you cleanup the timeout and you avoid any extra request

How to use synchronous and asynchronous functions inside onClick method in React

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]);

Categories