useState([]) dosen't work when I update an array - javascript

I have a problem. The variable interests is filled up but interestsInput not. What exactly is the problem here? I want to show the interests of a person in a textfield, but it doesnt show the input in the array.
const[firstLoad, setFirstLoad] = useState(true);
const [interestsInput, setInterests] = useState([]);
const selectedTags = (tags) => {
setTags(tags)
};
useEffect(() => {
if(firstLoad) {
getInterests();
}
}, [interestsInput]);
const getInterests = () => {
axios
.get("http://localhost:4000/interests",
{ }
)
.then((res) => {
if (res.status === 200) {
var interests = [];
for (var i = 0; i < firstInterest.length; i++) {
//console.log(myStringArray[i]);
//console.log(firstInterest[i]['text'])
//console.log(firstInterest[i])
interests[i] = firstInterest[i]['text'];
setInterests([...interestsInput, interests[i]])
}
console.log(interests);
//setInterests([]);
//setInterests([...interests]);
console.log(interestsInput)
}
})
.catch((error) => {
console.log(error);
});
}

Set the new interests outside of the loop, not inside it.
To make things concise, use .map to turn the array of objects into an array of texts.
const getInterests = () => {
axios
.get("http://localhost:4000/interests",
{}
)
.then((res) => {
if (res.status === 200) {
setInterests([...interestsInput, ...firstInterest.map(int => int.text)]);
} else {
throw new Error(res);
}
})
.catch((error) => {
console.log(error);
});
};

Also like user CertainPerformance suggested, you'll need to setState outisde the loop.
// this is redundant, you can achieve this by useEffect with empty array
// const[firstLoad, setFirstLoad] = useState(true);
const [interestsInput, setInterests] = useState([]);
const selectedTags = (tags) => {
setTags(tags)
};
const getInterests = () => {
axios
.get("http://localhost:4000/interests")
.then((res) => {
if (res.status === 200) {
var interests = [];
for (var i = 0; i < firstInterest.length; i++) {
interests[i] = firstInterest[i]['text'];
setInterests([...interestsInput, interests[i]])
}
}
})
.catch((error) => {
console.log(error);
});
}
// Ideally you'd want to put use effects after you have defined your constants & handlers
useEffect(() => {
// no need of firstLoad condition
getInterests();
}, []); // this will only run once after first render

Related

React Query Update Cached Data By Index Key

How do I update existing records by their index key?
Im not so familiar with React Query.
When a button is clicked, then this will trigger onClickHandler to update the object value by its index key.
import {useQuery, useQueryClient} from '#tanstack/react-query';
const {
data: comments,
isError,
isLoading
} = useQuery({
queryKey: ['comments'],
queryFn: async () => {
const response = await fetch(`https://jsonplaceholder.typicode.com/posts/1/comments`);
return response.json();
}
});
const onClickHandler = (index) => {
const previousData = queryClient.getQueriesData(['comments']);
queryClient.setQueryData(['comments'], (comments) => {
comments.map((r, i) => {
r['is_shown'] = false;
if(i === index) {
r['is_shown'] = true;
}
return r;
});
});
};
you forgot to return the new data inside setQueryData function.

setState in multiple fetch requests

I'am doing the StarWars API task for the job interview.
my code look like that and I don't know in what place the setCharacters hook should be.
First the page is rendered and then the state is set.
I need the page to be rendered when all the fetches are done.
To try to be more efficient i changed the previous fetches into Promise.all() but right now I'am stuck with the setCharacters placement.
The previous topic can be seen here useEffect efficiency in Star Wars API
const api = `https://swapi.dev/api/people/`;
const [characters, setCharacters] = useState([]);
const [fetched, setFetched] = useState(false);
useEffect(() => {
const fetchOtherData = (characters) => {
const charactersWithAllData = [];
characters.forEach((character) => {
const homeworld = character.homeworld;
const species = character.species;
const vehicles = character.vehicles;
const starships = character.starships;
let urls = [homeworld, ...species, ...vehicles, ...starships];
Promise.all(
urls.map((url) => {
if (url.length) {
fetch(url)
.then((response) => response.json())
.then((data) => {
if (url.search("species") > 0) {
character.species = data.name;
}
if (url.search("planets") > 0) {
character.homeworld = data.name;
}
if (url.search("vehicles") > 0) {
character.vehicles.shift();
character.vehicles.push(data.name);
}
if (url.search("starships") > 0) {
character.starships.shift();
character.starships.push(data.name);
}
})
.catch((err) => console.error(err));
}
if (!url.length) {
if (url.search("species")) {
character.species = "Unspecified";
}
if (url.search("vehicles")) {
character.vehicles = "";
}
if (url.search("starships")) {
character.starships = "";
}
}
})
).then(charactersWithAllData.push(character));
});
setCharacters(charactersWithAllData);
};
const fetchApi = () => {
const characters = [];
Promise.all(
[api].map((api) =>
fetch(api)
.then((response) => response.json())
.then((data) => characters.push(...data.results))
.then((data) => fetchOtherData(characters))
.then(setFetched(true))
)
);
};
fetchApi();
}, []);
Thanks for all the of the possible replies.
You can add a loading state, which by default is set to true. After you finish fetching the data from the api you can set loading to false and then render the page.
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchOrdersData = Promise.all().then((data) => {
//Fetch all data
.....
//Render page after loading is false
setLoading(false);
}).catch()
}, [])
Issue
Found a few issues:
Primary Issue: The fetchApi function's Promise.all chain was immediately invoking setFetched(true) instead of waiting for the chain's thenable to invoke it. This is why you are seeing the empty table before the data has been fetched and processed.
const fetchApi = () => {
const characters = [];
Promise.all(
[api].map((api) =>
fetch(api)
.then((response) => response.json())
.then((data) => characters.push(...data.results))
.then((data) => fetchOtherData(characters))
.then(setFetched(true)) // <-- immediately invoked
)
);
};
fetchOtherData function is also synchronous and doesn't return a Promise so the outer Promise-chain in fetchApi doesn't wait for the processing to complete, this is why you still see URL strings instead of the resolved values from the fetch.
Solution
Fix the Promise chain in fetchApi to be invoked in the .then callback. Rework the logic in fetchOtherData to wait for the additional fetch requests to resolve.
Here's my suggested solution:
const api = `https://swapi.dev/api/people/`;
function App() {
const basicClassName = "starWarsApi";
const [characters, setCharacters] = useState([]);
const [fetched, setFetched] = useState(false);
useEffect(() => {
const fetchOtherData = async (characters) => {
const characterReqs = characters.map(async (character) => {
const { homeworld, species, starships, vehicles } = character;
const urls = [homeworld, ...species, ...vehicles, ...starships];
// Make shallow copy to mutate
const characterWithAllData = { ...character };
const urlReqs = urls.map(async (url) => {
if (url.length) {
try {
const response = await fetch(url);
const { name } = await response.json();
if (url.includes("species")) {
characterWithAllData.species = name;
}
if (url.includes("planets")) {
characterWithAllData.homeworld = name;
}
if (url.includes("vehicles")) {
characterWithAllData.vehicles.shift();
characterWithAllData.vehicles.push(name);
}
if (url.includes("starships")) {
characterWithAllData.starships.shift();
characterWithAllData.starships.push(name);
}
} catch (err) {
console.error(err);
}
} else {
if (url.includes("species")) {
characterWithAllData.species = "Unspecified";
}
if (url.includes("vehicles")) {
characterWithAllData.vehicles = "";
}
if (url.includes("starships")) {
characterWithAllData.starships = "";
}
}
});
await Promise.all(urlReqs); // <-- wait for mutations/updates
return characterWithAllData;
});
return Promise.all(characterReqs);
};
const fetchApi = () => {
Promise.all(
[api].map((api) =>
fetch(api)
.then((response) => response.json())
.then((data) => fetchOtherData(data.results))
.then((charactersWithAllData) => {
setCharacters(charactersWithAllData);
setFetched(true);
})
)
);
};
fetchApi();
}, []);
return (
<div className={basicClassName}>
{fetched && (
<>
<h1 className={`${basicClassName}__heading`}>Characters</h1>
<div className={`${basicClassName}__inputsAndBtnsSection`}>
<FilteringSection
basicClassName={`${basicClassName}__inputsAndBtnsSection`}
/>
<ButtonsSection
basicClassName={`${basicClassName}__inputsAndBtnsSection`}
/>
</div>
<CharactersTable characters={characters} />
</>
)}
</div>
);
}

Can't iterate over my API generated array using the forEach() method

After troubleshooting with console.log/debugger it seems like I cannot iterate over my API generated array at the forEach method call in the function addListItem.
However I can see the pokemonNameList array being populated in the forEach iteration in the loadList function.
What am I doing wrong?
const apiUrl = 'https://pokeapi.co/api/v2/pokemon/?limit=15';
const pokemonNameList = [];
function getAll() {
return pokemonNameList;
}
function add(pokemon) {
if (typeof pokemon === 'object') {
pokemonNameList.push(pokemon);
}
}
function loadList() {
return fetch(apiUrl)
.then((response) => response.json())
.then((data) => {
data.results.forEach((item) => {
fetch(item.url)
.then((response) => response.json())
.then((inneritem) => {
const pokemon = {
name: inneritem.name,
height: inneritem.height,
weight: inneritem.weight
};
add(pokemon);
console.log(pokemonNameList);// I can see the array here
});
});
})
.then(() => {
console.log(pokemonNameList);
})
.catch((e) => {
console.error(e);
});
}
function addListItem(pokemon) {
console.log('I cannot see this console log');//This does not show up
const card = document.createElement('li');
const cardbody = document.createElement('div');
const name = document.createElement('h1');
card.classList.add('card');
cardbody.classList.add('card-body');
name.classList.add('card-title');
name.innerText = pokemon.name;
cardbody.appendChild(name);
card.appendChild(cardbody);
pokemonList.appendChild(card);
}
loadList()
.then(() => {
getAll().forEach((item) => {
console.log('Hello from inside the forEach');//I cannot see this
addListItem(item);
});
})
.catch((e) => {
console.error(e);
});
The problem is that you are not waiting for the inner fetch(item.url)s so when you call getAll no item has been pushed yet.
you can do that by changing forEach to map, returning the promise and adding a promise.all... something like this:
function loadList() {
return fetch(apiUrl)
.then((response) => response.json())
.then((data) => {
return Promise.all(data.results.map((item) => {
return fetch(item.url)
...
I created all the functions up to the place where you mentioned the error
const pokemonNameList = []; // Pokemon Array
const apiUrl = 'https://pokeapi.co/api/v2/pokemon/?limit=15'; // API URL
// To prevent duplicates, in case of calling the loadList function multiple times, i'm passing the index from the response, to replace the element at the same index
const add = (pokemon, index) => pokemonNameList[index] = (pokemon);
const getAll = _ => pokemonNameList; // Short arrow function to return pokemonNameList
async function loadList() {
const response = await fetch('https://pokeapi.co/api/v2/pokemon/?limit=5');
const result_1 = await response.json();
Promise.all(result_1.results.map((item, index) => fetch(item.url).then(response_1 => response_1.json()).then(({
name,
height,
weight
}) => add({
name,
height,
weight
}, index)))).then(() => getAll().forEach(pokemon => console.log(pokemon)));
}

How do I use async/await with Array.filter properly in React?

I'm creating just a simple currency converter (React + Typescript). Here is my component code:
const App = () => {
const [countries, setCountries] = useState<Array<CountriesProps>>([])
const [currencies, setCurrencies] = useState<Currencies>({})
const filteredCountries = async () => {
const { data } = await axios.get('https://restcountries.eu/rest/v2/all')
const answer: Array<CountriesProps> = data
const filtered = answer.filter(country => {
for (let i in currencies) {
if(i === country.currencies[0].code) {
return country
}
}
})
setCountries(filtered)
}
useEffect(() => {
axios
.get('https://api.frankfurter.app/currencies')
.then(res => {
setCurrencies(res.data)
})
.catch(err => {
console.log(err)
})
}, [])
useEffect(() => {
filteredCountries()
}, [])
return (
...
)
}
export default App
I come across the problem, during launching the app. After getting currencies information from the server I need to fetch countries information. After getting countries I need to filter them and put them in my state (countries) and send it to another component and so on. But during launch of the app filter function doesn't work and I got no filtered countries and so I don't have any info in my state. I think that filter function needs to be an asynchronous, so we need to wait before setting our state through setCountries function. How to do it properly in my case or I did all the logic wrong?
As long as requested countries rely on fetched currencies and you don't seem to be using one without the another, you may stack .get()-requests accordingly or use respective async...await alternative:
fetchData = async () => {
const currenciesResponse = await axios.get(currenciesEndpoint),
currenciesData = await currenciesResponse.data,
countriesResponse = await axios.get(countriesEndpoint),
countriesData = await countriesResponse.data,
filteredCountriesData = countriesData.filter(_country => {
const {
currencies: [{ code }]
} = _country;
return currenciesData[code];
});
setCurrencies(currenciesData);
setCountries(filteredCountriesData);
}
useEffect(() => {
fetchData();
}, [])
Following is a full-blown demo as a proof-of-a-concept
See if this helps.
const [countries, setCountries] = useState<Array<CountriesProps>>([])
const [currencies, setCurrencies] = useState<Currencies>({})
const filteredCountries = async () => {
const { data } = await axios.get('https://restcountries.eu/rest/v2/all')
const answer: Array<CountriesProps> = data
const filtered = answer.filter(country => {
return currencies[country.currencies[0].code]
})
setCountries(filtered)
}
useEffect(() => {
axios
.get('https://api.frankfurter.app/currencies')
.then(res => {
setCurrencies(res.data)
})
.catch(err => {
console.log(err)
})
}, [])
useEffect(() => {
filteredCountries()
}, [currencies])
try using this:
const App = () => {
const [countries, setCountries] = useState<Array<CountriesProps>>([])
const [currencies, setCurrencies] = useState<Currencies>({})
const filteredCountries = async () => {
const res = await axios.get('https://api.frankfurter.app/currencies')
// you don't need a state for currencies but in case you find a use case for it,
// you're just setting the currencies here for future use cases.
setCurrencies(res.data);
const { data } = await axios.get('https://restcountries.eu/rest/v2/all')
const answer: Array<CountriesProps> = data
const filtered = answer.filter(country => {
for (let i in res.data) {
if(i === country.currencies[0].code) {
return country
}
}
})
setCountries(filtered)
}
useEffect(() => {
filteredCountries()
}, [])
return (
...
)
}
export default App

javascript optimize .map to spread operator

I am using a recursive function to make async calls if there is an odata nextlink. It works fine as it is by using map to push the items into teamsArray. The problem hover is that I am looping through each item instead of merging the objects together. I tried to use the following but with no avail:
teamsArray = {}
teamsArray = { ...teamsArray, ...latstestResults}
Current code that does work but is not optimized:
export const fetchAllTeams = () => {
return dispatch => {
dispatch(fetchAllTeamsRequest());
};
};
export const fetchAllTeamsRequest = () => {
return dispatch => {
dispatch(getAllTeamStarted());
let teamsArray = [];
getAllTeams("", teamsArray, dispatch);
};
};
const getAllTeams = (url, teamsArray, dispatch) => {
if (url === "") {
url = "https://graph.microsoft.com/v1.0/me/memberOf?$top=10";
}
const getTeams = adalGraphFetch(fetch, url, {})
.then(response => {
if (response.status != 200 && response.status != 204) {
dispatch(fetchAllTeamsFailure("fout"));
return;
}
response.json().then(result => {
if (result["#odata.nextLink"]) {
const teams = objectToArray(result.value);
teams.map(team => {
teamsArray.push(team);
});
getAllTeams(result["#odata.nextLink"], teamsArray, dispatch);
} else {
const latestResult = objectToArray(result.value);
latestResult.map(team => {
teamsArray.push(team);
});
console.log("the teams", teamsArray);
dispatch(fetchAllTeamsSucces(result));
}
});
})
.catch(error => {
dispatch(fetchAllTeamsFailure(error));
});
};
Something like this might work for you.
I refactored the paged fetching into an async function that calls itself if there are more items to fetch, then eventually resolves with the full array of results.
Dry-coded, so there may be bugs and YMMV, but hope it helps.
export const fetchAllTeams = () => {
return dispatch => {
dispatch(fetchAllTeamsRequest());
};
};
export const fetchAllTeamsRequest = () => {
return async dispatch => {
dispatch(getAllTeamStarted());
try {
const teamsArray = await getPaged(
"https://graph.microsoft.com/v1.0/me/memberOf?$top=10",
);
dispatch(fetchAllTeamsSucces(teamsArray));
} catch (err) {
dispatch(fetchAllTeamsFailure(err));
}
};
};
const getPaged = async (url, resultArray = []) => {
const response = await adalGraphFetch(fetch, url, {});
if (response.status != 200 && response.status != 204) {
throw new Error("failed to fetch teams");
}
const result = await response.json();
objectToArray(result.value).forEach(team => resultArray.push(team));
if (result["#odata.nextLink"]) {
// Get more items...
return getPaged(resultArray, result["#odata.nextLink"]);
}
return resultArray; // All done, return the teams array.
};

Categories

Resources