After Promise object not rendering - javascript

I have been trying to display map an array after resolving its promise. Unfortunately, it is not being rendered. Here is the code:
<View>
{array.map((item) => {
promiseFunction(item).then((res) => {
return (
<FunctionalComponent
prop={res.prop}
/>
);
});
})}
</View>

You have to separate the async function from rendering. And you have to do the array.map properly. The list of FunctionalComponent props must be a state.
const [propsArray, setPropsArray] = useState([])
useEffect(() => { // or any other function, that will feed data
array.map(item => {
promiseFunction(item).then(res => {
setPropsArray(p => [...p, res])
}
},
[])
return (<View>
{propsArray.map(c => (<FunctionalComponent prop={c.prop} />) }
</View>)

Array.prototype.map() won't wait for Promise to resolve, so data will never be rendered. You should try classic for ... of.
const Foo = ({}) => {
const [components, setComponents] = useState([])
useEffect(() => {
(async () => {
for (const item of array) {
setComponents([...conponents, <FunctionalComponent prop={(await promiseFunction(item)).prop}/>])
}
})()
}
return (
<View>
{components}
</View>
)
}

You can't do that directly as you are trying to since React will try to render an array of promises and when they have resolved it's too late the Component has already rendered. Previous answers showd you how to set to state the results once they come and render only after the promises are fulfilled. But with React#18 you could jump that step and render the promises almost directly.
If you are using Suspense, there is a way to handle this kind of scenarios, because Suspended components are able to consume promises directly and to render a fallback until they resolve:
const arr = [1, 2, 3, 4, 5];
export default function App() {
return (
<Suspense fallback={<p>Loading.....</p>}>
<AsyncComponent />
</Suspense>
);
}
export const AsyncComponent = () => {
// THIS IS AN ARRAY OF PROMISES
const data = arr.map((el, i) => {
const d = useGetData(`https://jsonplaceholder.typicode.com/todos/${i + 1}`);
console.log(d);
return (
<h5>
{i + 1}:{d?.title}
</h5>
);
});
// HERE YOU RENDER THE PROMISES DIRECTLY
return <div>{data}</div>;
};
A demo that you can play with, HERE.

Related

react native state is updated but functions still using the initial state set at the time of mounting

In my react native functional component, the state gets updated but when I want to use this state inside a function (for e.g, to send data to API), it uses the initial state only.
imports...
const Component = ({ navigation }) => {
const [ids, setIds] = useState([1,2]);
useLayoutEffect(() => {
navigation.setOptions({
headerRight: () => <HeaderRight
onPress={() =>
console.log(ids); // this logs initial state, i.e, [1,2]
updateIdsToServerViaAPI(); // and therefore I'm unable to update ids using this method
}
/>
});
}, [navigation]);
const updateIdsToServerViaAPI = async () => {} // function that takes updated ids from state.
const onPress = async () => {
const newIds = [...ids, 3, 4];
setIds(newIds);
}
const onPressInsideComp = () => {
console.log(ids);
// here updated ids gets logged.
}
return (
<View>
<Button onPress={onPress} />
{ids.map(id => (
<Text key={id}>{id}</Text> {\* Here you will see 4 texts after pressing button, that means state gets updated*\}
)}
<Button onPress={onPressInsideComp} />
</View>
);
}
Seems like this issue happens only when functions are called inside useLayoutEffect or useEffect but when I call onPressInsideComp from the button inside the component, it logs properly!
I am badly stuck on this weird issue !!
You have only provided the navigation prop in the dependency array of your useLayoutEffect wrapper, thus the function is not recreated if the ids state changes.
You might want to create a different function, wrapped inside a useCallback which gets the ids state as a dependency and provide this function in your useLayoutEffect.
const doSomething = useCallback(() => {
console.log(ids);
updateIdsToServerViaAPI();
}, [ids])
useLayoutEffect(() => {
navigation.setOptions({
headerRight: () => <HeaderRight
onPress={() =>
doSomething(ids)
}
/>
});
}, [navigation, ids, doSomething]);
In your code, the console.log(ids) is resolved at the moment of the function definition, and not at execution time, so it takes the reference you get in the definition const [ids, setIds} = useState([1,2]).
Maybe just try to get your ids with a function of state instead of using a variable that has been defined before:
const [ids, setIds] = useState([1,2]);
const get_ids = () => this.state.ids;
useLayoutEffect(() => {
navigation.setOptions({
headerRight: () => <HeaderRight
onPress={() =>
console.log(get_ids());
updateIdsToServerViaAPI();
}
/>
});
}, [navigation]);

Promise only resolves correctly on page refresh

I am playing around with an API that gets a list of Pokemon and corresponding data that looks like this.
export function SomePage() {
const [arr, setArray] = useState([]);
useEffect(() => {
fetchSomePokemon();
}, []);
function fetchSomePokemon() {
fetch('https://pokeapi.co/api/v2/pokemon?limit=5')
.then(response => response.json())
.then((pokemonList) => {
const someArray = [];
pokemonList.results.map(async (pokemon: { url: string; }) => {
someArray.push(await fetchData(pokemon))
})
setArray([...arr, someArray]);
})
}
async function fetchData(pokemon: { url: string; }) {
let url = pokemon.url
return await fetch(url).then(async res => await res.json())
}
console.log(arr);
return (
<div>
{arr[0]?.map((pokemon, index) => (
<div
key={index}
>
{pokemon.name}
</div>
))
}
</div>
);
}
The code works(kind of) however on the first render the map will display nothing even though the console.log outputs data. Only once the page has been refreshed will the correct data display. I have a feeling it's something to do with not handling promises correctly. Perhaps someone could help me out.
TIA
Expected output: Data populated on initial render(in this case, pokemon names will display)
The in-build map method on arrays in synchronous in nature. In fetchSomePokemon you need to return a promise from map callback function since you're writing async code in it.
Now items in array returned by pokemonList.results.map are promises. You need to use Promise.all on pokemonList.results.map and await it.
await Promise.all(pokemonList.results.map(async (pokemon: { url: string; }) => {
return fetchData.then(someArray.push(pokemon))
}));
On your first render, you don't have the data yet, so arr[0] doens't exist for you to .map on it, so it crashes. You need to check if the data is already there before mapping.
Using optional chaining, if there's no data it will not throw an error on your first render and it will render correctly when the data arrive and it re-renders.
...
return (
<div>
{arr[0]?.map((pokemon, index) => (
<div key={index}>{pokemon.name}</div>
))}
</div>
);
}
in
useEffect(() => { fetchSomePokemon(); }, []);
[] tells react there is no dependencies for this effect to happen,
read more here https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects
One way to solve your issues is to await the data fetching in useEffect().
Here's a POC:
export function Page() {
const [pokemon, setPokemon] = useState([]);
// will fetch the pokemon on the first render
useEffect(() => {
async function fetchPokemon() {
// ... logic that fetches the pokemon
}
fetchPokemon();
}, []);
if (!pokemon.length) {
// you can return a spinner here
return null;
}
return (
<div>
{pokemon.map(item => {
// return an element
})}
</div>
);
}

How to render based on AJAX result in react js?

Just want to render movie cards based on results that come from ajax call.
Currently, the movie cards components are rendered based on that hard code array named list. I just want to make it dynamic and replace it with my ajax data.
const getlist = async () => {
const res = await fetch('http://localhost:3001/customize');
const data = await response.json();
getlist();
};
export default function Index() {
const list = ['dexter', 'bb', 'got'];
return (
<>
<main className={parentstyle.main_container}>
<NavBar />
<div className={style.searchbar_container}>
<SearchBar />
</div>
<div className={style.card_container}>
{test.map((element, i) => {
return <MovieCard movieName={element} key={i} />;
})}
</div>
</main>
</>
);
}
Use the useState hook to set up your component state (the list) and fetch data in a useEffect hook...
The Effect Hook lets you perform side effects in function components:
Data fetching, setting up a subscription, and manually changing the DOM in React components are all examples of side effects. Whether or not you’re used to calling these operations “side effects” (or just “effects”), you’ve likely performed them in your components before.
import { useEffect, useState } from "react"
const getlist = async () => {
const res = await fetch("http://localhost:3001/customize")
if (!res.ok) {
throw new Error(`${res.status}: ${await res.text()}`)
}
return res.json()
}
const Index = () => {
const [ list, setList ] = useState([]) // start with an empty array
useEffect(() => {
getList()
.then(setList)
.catch(console.error)
}, []) // empty dependencies array, this runs only once
return (
// ...
{list.map((element, i) => (
<MovieCard movieName={element} key={i} />
))}
// ...
)
}
export default Index

Javascript Promise.all results from fetching multiple apis not rendering in the React Native FlatList

I am using Promise.all in order to fetch multiple apis.
const ListScreen = () => {
const first = fetch('https://EXAMPLEAPI').then(resp => resp.json())
const second = fetch('https://EXAMPLEAPI').then(resp => resp.json())
const third = fetch('https://EXAMPLEAPI').then(resp => resp.json())
const retrieveAll = async function () {
let results = await Promise.all([first, second, third])
When console.log(results), I get all arrays of objects from apis
The problem is that when I create a FlatList, I don't get anything to be rendered on the screen(blank)
const retrieveAll = async function () {
let results = await Promise.all([first, second, third])
return (
<FlatList
keyExtractor={item => item.title}
data={results}
renderItem={({ item }) => {
return <Text>{item.title}</Text>
}}
/>
)
};
}
export default ListScreen;
What am I doing wrong?
Please help. :(
You need to re-render the component, for that you will have to use react Hooks.
This is how the component will look like
const RetrieveAll = function () {
const [ results, setResults ] = useState([])
useEffect( () => {
Promise.all([first, second, third])
.then(response => {
setResults(response)
})
}, [])
return (
<FlatList
keyExtractor={item => item.title}
data={results}
renderItem={({ item }) => {
return <Text>{item.title}</Text>
}}
/>
)
};
Usage
<RetrieveAll />
And try not to create async JSX elements.

Rerender Flatlist after Call api then setState

I just started learning React native, and want to render FlaList after setState.
I am try to call Api to get Data and then I sorting that data but the FlatList is not rerender with newData. I also try extraData but nothing happen. Where am I missing?
Thank for your help.
function HomeScreen(props) {
const {transactions = []} = useSelector(selectors.transactions) || [];
const [listTransaction, setListTransaction] = useState([]);
useEffect(() => {
dispatch(BalanceActions.balanceRequest()); // This is my call Api
sortListTransaction(); // I call sortFunc after that
}, []);
const sortListTransaction = () => { // In this function I group by date the array of the result Api
let groups = [];
transaction.forEach((item) => {
let date = moment(item.date).format('MM/DD/YYYY');
if (date in groups) {
groups[date].push(item);
} else {
groups[date] = new Array(item);
}
});
setListTransaction(groups);
};
const _renderItem = ({item}) => {
return <BodyContent data={item} />;
};
// Then my FlatList like:
return (
<FlatList
data={listTransaction}
keyExtractor={(item) => item.id}
renderItem={_renderItem}
extraData={listTransaction}
/>
)
}

Categories

Resources