I'm working with React, and in my component every time the user types a input the code calls a API.
But this API takes more time to return with few words than with bigger words.
So lets say i type "ne", and takes 7 seconds to return 100+ results, but before that 7 seconds, i wait a second and finish type "new york". That takes miliseconds, than finishes before the previus API call.
Ok, "new york" appears to me on the search, BUT now the first call finishes and overrites the last result.
How can i make that work? Without breaking any of the steps (aka, search in a click of a button stead while tiping), if this is possible
Short example of my code:
class MyComponent extends Component {
state = {
items = []
}
construtor(props) {
this.myCall = _debounce(this.myCall, 400);
}
myCall = async e => {
e.persist();
const query = _get(e, 'target.value', '');
try {
items = await myAPI(query)
this.setState({items})
} catch (error) {
console.error(error)
}
}
...
return (
<>
<input onChange={(e) => myCall(e)} />
{items.map(item => (
<p>item</p>
))}
</>
);
}
You could check that the input value hasn't changed while you awaited the response:
items = await myAPI(query)
if (query === _get(e, 'target.value', '')) {
this.setState({items})
}
Same thing with an implementation that avoids multiple _get calls:
const query = this.query = _get(e, 'target.value', '');
items = await myAPI(query)
if (query === this.query) {
this.setState({items})
}
Related
I am pulling documents from Firebase, running calculations on them and separating the results into an array. I have an event listener in place to update the array with new data as it is populated.
I am using setTimeout to loop through an array which works perfectly with the initial data load, but occasionally, when the array is updated with new information, the setTimeout glitches and either begins looping through from the beginning rather than continuing the loop, or creates a visual issue where the loop doubles.
Everything lives inside of a useEffect to ensure that data changes are only mapped when the listener finds new data. I am wondering if I need to find a way to get the setTimeout outside of this effect? Is there something I'm missing to avoid this issue?
const TeamDetails = (props) => {
const [teamState, setTeamState] = useState(props.pushData)
const [slide, setSlide] = useState(0)
useEffect(() => {
setTeamState(props.pushData)
}, [props.pushData])
useEffect(()=> {
const teams = teamState.filter(x => x.regData.onTeam !== "null" && x.regData.onTeam !== undefined)
const listTeams = [...new Set(teams.map((x) => x.regData.onTeam).sort())];
const allTeamData = () => {
let array = []
listTeams.forEach((doc) => {
//ALL CALCULATIONS HAPPEN HERE
}
array.push(state)
})
return array
}
function SetData() {
var data = allTeamData()[slide];
//THIS FUNCTION BREAKS DOWN THE ARRAY INTO INDIVIDUAL HTML ELEMENTS
}
SetData()
setTimeout(() => {
if (slide === (allTeamData().length - 1)) {
setSlide(0);
}
if (slide !== (allTeamData().length - 1)) {
setSlide(slide + 1);
}
SetData();
console.log(slide)
}, 8000)
}, [teamState, slide]);
Im developing a simple app and i need to save a couple of user data so im using AsyncStorage like that:
const storeAge = async (age) => {
try {
console.log("STORED age: " + age);
await AsyncStorage.setItem("#age", age);
} catch (e) {
// saving error
}
};
And im calling this function here:
<SetupInfo
visible={isStartUp}
onRegist={registHandler}
onRegistDataSave={storeAge}
/>
The onRegistDataSave is called on a press of a button on the SetupInfo component:
const addRegistHandlerMemory = () => {
if (!isEnabled) {
props.onRegistDataSave(age / 12);
} else {
props.onRegistDataSave(age);
}
setAge("");
};
...
<Button
title="REGIST"
onPress={() => {
addRegistHandler();
addRegistHandlerMemory();
}}
style={styles.button}
/>
The console.log on store.age is showing the correct input when button is pressed, but when i reload the app and call this:
const getAge = async () => {
try {
const value = await AsyncStorage.getItem("#age");
if (value !== null) {
console.log("valor" + value)
setAppMode(false);
setAge(parseFloat(value));
}
console.log("age " + value);
} catch (e) {
// error reading value
}
};
the value I got is null, why is this happening?
(Well i think i know becouse im doing the same think to save data on one array and the last value is not saved which means that probably is saving on the next render that for me - a noob on this- doent make sense since the set age is an async funtion that should avoid that...)
Oh I figure it out where the error was. Turns out I was saving a number and AsyncStorage just saves Strings, so i just need to add const string = toString(age); on storeAge and save that value.
I am using hooks in React Native. This is my code:
useEffect(() => {
if (var1.length > 0 ){
let sym = [];
var1.map((items) => {
let count = 0;
state.map((stateItems) => {
if(items === stateItems.name) {
count = count + 1;
}
})
if (count === 0) {
sym.push(items)
}
});
async function getAllStates (sym) {
let stateValues = [];
await Promise.all(sym.map(obj =>
axios.get(ServerURL + "/note?name=" + obj).then(response => {
stateValues.push(response.data[0]);
})
)).then(() =>{
setNewItem(stateValues);
});
}
getAllStates (sym);
}
}, [var1]);
useEffect(() => {
let stateValues = state;
for( let count = 0 ; count < newItem.length; count++ ){
stateValues.push(newItem[count]);
}
setState(stateValues);
}, [newItem]);
This runs successfully without any errors. However, when the state is displayed as below, I am not seeing the latest value added in the state. It shows all the previous values. When I refresh the whole application, I am able to see my value added.
return (
<View style={styles.container}>
<Text style = {{color:"white"}}>
{
state.map( (item, key) =>{
return(
<Text key = {key} style = {{color:"white"}}> {item.name} </Text>
)
})
}
</Text>
</View>
);
Can someone tell me why this is happening? I want to see the data render immediately after the axios call. I am using React Native.
when i force update using :stackoverflow.com/questions/53215285/... it works fine. However, i am looking for a better fix if anyone can provide?
This should do:
useEffect(() => {
var1.forEach(async (name) => {
if (state.some(item => item.name === name)) return;
const response = await axios.get(ServerURL + "/note?name=" + name);
setState(state => [...state, response.data[0]]);
});
}, [var1]);
I still see two issues in your approach:
this code may start the same ajax request multiple times before the result of the firstz ajax-request is added to state; this also means that the result for the same name may be added multiple times to state.
for each item of var1 times each item of state, this is an O(n*m) problem or in this case basically O(n²) as m is pretty fast catching up to n. You should find a better approach here.
And I'm almost certain that [var1] is wrong here as the dependency for useEffect. But you'd need to show where this value comes from to fix that, too.
I'm teaching myself React and one of my exercises is using axios to fetch a list of countries from an API
const fetchCountries = () => {
axios.get("https://restcountries.eu/rest/v2/all").then(response => {
setCountries(response.data);
});
};
React.useEffect(fetchCountries, []);
Then as a user types into an input the list of countries filters down.
const handleInputChange = event => {
const filter = event.target.value; // current input value
let matchingCountries = query !== ''
? countries.filter(country => country.name.toLowerCase().indexOf(query.toLowerCase()) !== -1)
: countries;
setQuery(filter);
setMatches(matchingCountries)
console.log('matches', matches)
console.log('query', query)
};
My goal is that when a single country is matched, a new API request is triggered (to fetch the weather, but the what isn't my problem, the timing is). When a single country is matched, I will then render some data about the country, then fetch and render the weather details for the single country's capital city.
One of the problems I'm having is that when I set the state, the value always seems to be one step behind. For example, in this Codepen when you enter FRA you should get "France". However, I have to enter "FRAN" to get the match. This doesn't happen when I don't use a state variable for the matches (just let matches). This becomes a problem because I need to run the next API call when the number of matches = 1, but the length of the matches state is always wrong.
So I would like to know 1. how to get the correct state of the matched countries. And 2. when I should run the second API call without getting into an infinite loop.
useEffect solution using separation of concern
1 function should do 1 thing
handleInputChange updates state
useEffect updates state
But they are not coupled.
Later you might have a new function called handleDropdownChange which updates state
It that case you don't need to modify useEffect
At the end of the day, we (developers) don't like to rewrite things
const [countries, setCountries] = React.useState([]);
const [query, setQuery] = React.useState("");
const [matches, setMatches] = React.useState([]);
React.useEffect(() => {
let matchingCountries = query !== ''
? countries.filter(country => country.name.toLowerCase().indexOf(query.toLowerCase()) !== -1)
: countries;
setMatches(matchingCountries)
}, [query]); // called whenever state.query updated
const handleInputChange = event => {
setQuery(event.target.value); // update state
};
const fetchCountries = () => {
axios.get("https://restcountries.eu/rest/v2/all").then(response => {
setCountries(response.data);
});
};
React.useEffect(fetchCountries, []);
And there is also solution (not recommended) by directly using event.target.value provided by #Joseph D.
The only problem is you are using an old query value in handleInputChange().
Remember setting the state is asynchronous (i.e. doesn't take effect immediately)
Here's an updated version:
const handleInputChange = event => {
const filter = event.target.value; // current input value
let matchingCountries = filter ? <code here>
// ...
setQuery(filter);
};
UPDATE:
To call the weather api if there's a single country match is to have matches as dependency in useEffect().
useEffect(
() => {
async function queryWeatherApi() {
// const data = await fetch(...)
// setData(data)
}
if (matches.length === 1) {
queryWeatherApi();
}
},
[matches]
)
1) The reason for your problem is in this line:
let matchingCountries = filter !== ''
? countries.filter(country => country.name.toLowerCase().indexOf(query.toLowerCase()) !== -1)
: countries;
you use query instead of filter variable, your handler function should look like this:
const handleInputChange = event => {
const filter = event.target.value; // current input value
let matchingCountries = filter !== ''
? countries.filter(country => country.name.toLowerCase().indexOf(filter.toLowerCase()) !== -1)
: countries;
setQuery(filter);
setMatches(matchingCountries)
};
2) Where to run your next API call:
For studying purpose I do not want to recommend you using some application state management lib like redux.Just calling it right after setFilter and setQuery. It will run as expected. Because calling an API is asynchronous too so it will be executed after setQuery and setFilter what does not happen with console.log, a synchronous function.
I have search functionality that allows you to search for cryptocurrencies and I'm trying to have it where if the param/coin that is searched doesn't match with the API's title of the coin or symbol, an alert box would pop up telling the user to try again.
The problem I'm having is that if the if statement is true and there's a match, the else statement/alert box pops up regardless. Here's my code within my axios request:
cryPrice(param){
axios.get(`https://api.coinmarketcap.com/v1/ticker/?limit=0`)
.then(res => {
const cryptos = res.data;
const price = []
for(let title in cryptos){
const cryptoTitle = cryptos[title].name.toUpperCase();
const cryptoSymbol = cryptos[title].symbol.toUpperCase();
if(cryptoTitle === param || cryptoSymbol === param){
price.push(cryptos[title].price_usd);
}else{
alert("Please try again");
break;
}
}
this.setState({
priceResults: price
})
})
}
Your code loops through every single item in the array, as soon as a non-matching item is encountered it alerts and breaks the loop
What you probably want is more like
cryPrice(param){
axios.get(`https://api.coinmarketcap.com/v1/ticker/?limit=0`)
.then(res => {
const priceResults = res.data
.filter(item => [item.name.toUpperCase(), item.symbol.toUpperCase()].includes(param))
.map(item => item.price_usd);
if (priceResults.length) {
this.setState({priceResults});
} else {
alert("Please try again");
}
})
}
You find a match on some of the elements but alert about every other, all of those that do not match param. You should probably only alert after the loop execution has stopped and iff there were no currencies matched.