Get data from API by map function - reactjs

I'm running into a problem that I've been working on for days and unfortunately I can't figure it out by myself. I'm trying to create a View which shows some information from an API. But every time I map this item, I want to do another API call which checks the live price of that product.
So I have for example some JSON data what I get from an API.
{
"id": 1,
"name": "test product",
"productid": "73827duf"
},
{
"id": 2,
"name": "test product2",
"productid": "734437dde"
}
So I show this data with the following code inside my application:
{item.products.map((products) => {
return (
<View
key={products.id}
>
<Text
style={{
fontSize: FONTS.body3,
paddingLeft: 10,
}}
>
{products.name}
{getProductPriceJumbo(
products.productid
)}
</Text>
</View>
);
})}
So I want to run every time a function which fetches data from another API. I'm sending the productID because that's the only information I need to call this API. You can see this function down below:
function getProductPriceJumbo(id) {
fetch("https://---/test.php?id=" + id + "/", {
method: "GET",
})
.then((response) => response.json())
.then((data) => {
return data[0].price;
});
}
So this fetch returns a big list with information about the product from a third party API. I only want to return the price, that's the reason why I only return the price value and I want to print this out on the view above. I can't really figure out how to do this. I get undefined from the function every time I run it. Hope someone can help me with this.

Create a new Price Component to display the price
function Price({ id }) {
const [price, setPrice] = useState(0);
useEffect(() => {
function getProductPriceJumbo(id) {
fetch("https://---/test.php?id=" + id + "/", {
method: "GET"
})
.then((response) => response.json())
.then((data) => {
setPrice(data[0].price);
});
}
getProductPriceJumbo(id);
},[]);
return <Text>{price}</Text>;
}
And your .map will become
{
item.products.map((products) => {
return (
<View key={products.id}>
<Text
style={{
fontSize: FONTS.body3,
paddingLeft: 10
}}
>
{products.name}
<Price id={products.productid} />
</Text>
</View>
);
});
}

The reason you are getting undefined is because the window is rendering before the function finishes running. You will have define an asynchronous function before you return your view.
const [data, setData] = useState([])
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchData = async () =>{
setLoading(true);
try {
const {data: response} = await axios.get('API URL');
setData(response);
} catch (error) {
console.error(error.message);
}
setLoading(false);
}
fetchData();
}, []);
Then you can use data[0].price;

You'll probably want to make your individual product into its own component that handles the fetching, and setting the price to a state value that's local to that product view. Here's a full example of how you could do that:
import { useState, useEffect } from "react";
const Product = ({ product }) => {
const [price, setPrice] = useState("Price loading...");
useEffect(() => {
fetch("https://---/test.php?id=" + product.productid + "/", {
method: "GET"
})
.then((response) => response.json())
.then((data) => {
setPrice(data[0].price);
});
}, [product]);
return (
<View>
<Text
style={{
fontSize: FONTS.body3,
paddingLeft: 10
}}
>
{product.name}
{price}
</Text>
</View>
);
};
const App = () => {
const item = {
products: [
{
id: 1,
name: "test product",
productid: "73827duf"
},
{
id: 2,
name: "test product2",
productid: "734437dde"
}
]
};
return (
<div>
{item.products.map((product) => (
<Product key={product.id} product={product} />
))}
</div>
);
};
Alternatively, you could use Promise.all to get all of the price values before mapping your products:
import { useState, useEffect } from "react";
const App = () => {
const [item] = useState({
products: [
{
id: 1,
name: "test product",
productid: "73827duf"
},
{
id: 2,
name: "test product2",
productid: "734437dde"
}
]
});
const [products, setProducts] = useState([]);
useEffect(() => {
Promise.all(
item.products.map(async (product) => {
const response = await fetch(
`https://---/test.php?id=${product.productid}/`
);
const data = await response.json();
return {
...product,
price: data[0].price
};
})
).then((products) => setProducts(products));
}, [item]);
return (
<div>
{products.map((product) => {
return (
<View key={product.id}>
<Text
style={{
fontSize: FONTS.body3,
paddingLeft: 10
}}
>
{product.name}
{product.price}
</Text>
</View>
);
})}
</div>
);
};

Related

Why element is not getting rendered and how can I fix it?

I am trying to render some dynamic data in an element using useEffect Hook, which is not working.
Below is abc.tsx which has byTestOne(globalThis.test) which is responsible to send data back to us using globalThis.test value
const abc = () => {
const [data, setData] = useState<any>([]);
// globalThis.test is something that changes and when it changes, byTestOne triggers and get the data based on globalThis.test
useEffect(() => {
async function fetchData() {
const data = byTestOne(globalThis.test).then((data: any) => {
return data
})
setData(await data)
}
fetchData();
}, [globalThis.test]);
return (
<ImageBackground
source={require("../assets/images/abc.png")}
style={styles.bg}>
<View style={styles.containerHome}>
<CardStack>
{
// Below Element is not getting rendered
data.map((item: any) => (
<Card key={item.id}>
<CardItemForSwiper
name={item.name}
description={item.description}
/>
</Card>
)
)
}
</CardStack>
</View>
</ImageBackground>
);
};
export default ABC;
byTestOne() looks like this:
let data = [{
name: "test1",
description: "desc",
test: "one"
},
{
name: "test2",
description: "desc",
test: "two"
}]
export const byTestOne = (test: string) => {
const dataA = new Promise((resolve) => {
const filData = data.filter((rawData) => {
return rawData.test == test
})
if (filData.length > 0) {
globalThis.test = test
}
return resolve(filData)
})
return dataA
}

How to call api from each of the element rendered

I wonder if calling api for every element rendering is possible. The code below didn't work for me.
export default function App() {
const items = [
{ title: 1, description: "description1" },
{ title: 2, description: "description2" }
];
const finalTitleByApi = async (title) => {
const response = await fetch(
`https://jsonplaceholder.typicode.com/todos/${title}`
).then((response) => response.json());
return response;
};
return (
<div>
{items.map((item) => {
return (
<p>
{finalTitleByApi(item.title).title}
</p>
);
})}
</div>
);
}
What is wrong with the code above. Any help is will be appreciated. Thank you.
This is the example codesandbox link https://codesandbox.io/s/for-each-rendered-element-that-calls-api-pmcnn6?file=/src/App.js:879-886
Try to use react-async library , hope will be helpful react-async
To trigger the async function use useEffect to invoke it during initial rendering of the component as follows. Additionally, you can use a state to manage it as well.
const [responseState, setResponseState] = useState(null);
const finalTitleByApi = async () => {
const response = await fetch(
"https://jsonplaceholder.typicode.com/todos/1"
).then((response) => response.json());
console.log("Response: ", response);
setResponseState(response);
};
useEffect(() => {
finalTitleByApi();
}, []);
useEffect(() => {
console.log("Response State: ", responseState);
}, [responseState]);
One solution that I think is
import React, { useEffect } from "react";
import "./styles.css";
import {useApplicationContext} from './Context';
export default function App() {
const {titles, setTitles} = useApplicationContext();
const items = [
{ title: "1", description: "description1" },
{ title: "5", description: "description2" },
{ title: "8", description: "description2" },
{ title: "9", description: "description2" },
{ title: "10", description: "description2" },
{ title: "24", description: "description2" }
];
const makeDivs = () => {
let a = {};
items.map(async (item) => {
const res = await fetch(
`https://jsonplaceholder.typicode.com/todos/${item.title}`
).then(response => response.json());
a[item.title] = res.title;
setTitles((prevState) => {
return {...a}
});
})
}
React.useEffect(()=> {
makeDivs()
}, [])
// console.log(a )
return (
<div>
{JSON.stringify(titles)}
{items.map((item, index) => {
return (
<p
key={Math.random()}
style={{
color: "black",
backgroundColor: "yellow",
height: 400,
width: 400
}}
>
<span>index: {item.title} {titles && titles[item.title]}</span>
</p>
);
})}
</div>
);
}
used Context provider for not re-render component state
link of sandbox is Sandbox

How to console.log the

I have a simple React component and inside of it I am fetching data from a remote API, and I want to console.log it in useEffect. I am trying to do it but nothing doesn't get logged into the console, why? What am I missing here? Here is the component:
import React, { useState, useEffect } from 'react';
import { useLocalization } from '#progress/kendo-react-intl';
import { Card, CardHeader, Avatar, CardTitle, CardSubtitle } from '#progress/kendo-react-layout';
import { guid } from '#progress/kendo-react-common';
import { Scheduler } from './../components/Scheduler';
import { employees } from './../resources/employees';
import { images } from './../resources/images';
import { orders, ordersModelFields } from './../resources/orders';
import { teams } from './../resources/teams';
// const orderEmployees = employees.filter(employee => employee.jobTitle === 'Sales Representative');
// const initialFilterState = { };
// orderEmployees.forEach(employee => {
// if(employee.fullName === 'Wait Peperell') {
// initialFilterState[employee.id] = false;
// } else {
// initialFilterState[employee.id] = true;
// }
// });
const Planning = () => {
const localizationService = useLocalization();
const [filterState, setFilterState] = React.useState(initialFilterState);
const [data, setData] = React.useState(orders);
const [fetchedData, setFetchedData] = React.useState(null);
useEffect(() => {
fetch("https://mocki.io/v1/29b83c0b-1a55-430d-a173-92b3632e04aa")
.then(response => response.json())
// 4. Setting *dogImage* to the image url that we received from the response above
.then(data => setFetchedData(data))
console.log(fetchedData)
},[])
// console.log(fetchedData)
const onDataChange = React.useCallback(
({ created, updated, deleted }) => {
setData(old => old
// Filter the deleted items
.filter((item) => deleted.find(current => current[ordersModelFields.id] === item[ordersModelFields.id]) === undefined)
// Find and replace the updated items
.map((item) => updated.find(current => current[ordersModelFields.id] === item[ordersModelFields.id]) || item)
// Add the newly created items and assign an `id`.
.concat(created.map((item) => Object.assign({}, item, { [ordersModelFields.id]: guid() }))))
},
[]
);
const onEmployeeClick = React.useCallback(
(employeeId) => {
setFilterState({
...filterState,
[employeeId]: !filterState[employeeId]
});
},
[filterState, setFilterState]
);
return (
<div id="Planning" className="planning-page main-content">
<div className="card-container grid">
<h3 className="card-title">{localizationService.toLanguageString('custom.teamCalendar')}</h3>
{
orderEmployees.map(employee => {
return (
<div
key={employee.id}
onClick={() => onEmployeeClick(employee.id)}
style={!filterState[employee.id] ? {opacity: .5} : {}}
>
<Card style={{ borderWidth: 0, cursor: 'pointer'}}>
<CardHeader className="k-hbox" >
<Avatar type='image' shape='circle' size={'large'} style={{
borderWidth: 2,
borderColor: teams.find(({teamID}) => teamID === employee.teamId).teamColor,
}}>
<div className="k-avatar-image" style={{
backgroundImage: images[employee.imgId + employee.gender],
backgroundSize: 'cover',
backgroundPosition: 'center center',
}}
/>
</Avatar>
<div>
<CardTitle style={{color: teams.find(({teamID}) => teamID === employee.teamId).teamColor}}>{employee.fullName}</CardTitle>
<CardSubtitle>{employee.jobTitle}</CardSubtitle>
</div>
</CardHeader>
</Card>
</div>
);
})
}
<div className="card-component" >
<Scheduler
data={data.filter(event => filterState[event.employeeID])}
onDataChange={onDataChange}
modelFields={ordersModelFields}
resources={[
{
name: 'Teams',
data: teams,
field: 'teamID',
valueField: 'teamID',
textField: 'teamName',
colorField: 'teamColor'
}
]}
/>
</div>
</div>
</div>
);
}
export default Planning;
I also tried to place the console.log outside of useEffect but still, nothing gets console.logged.
You need to look how useEffect work, setFetchedData is async.
Create another useEffect only for console.log.
useEffect(() => {
console.log(fetchedData);
},[fetchedData]); // Update at the first render + when fetchedData state change.
You can do it like this
useEffect(() => {
fetch("https://mocki.io/v1/29b83c0b-1a55-430d-a173-92b3632e04aa")
.then((response) => response.json())
// 4. Setting *dogImage* to the image url that we received from the response above
.then((data) => {
setFetchedData(data);
console.log(data);
});
}, []);
or juste create another useEffect that listens to fetchedData change, like this
useEffect(() => {
console.log(fetchedData);
}, [fetchedData]);

React Native Deck Swiper

I am trying to make a GET request to an enpoint using the two functional components below to display in my react native deck swipper
//using fetch
const getDataUsingFetch = () => {
fetch(latestNews+ApiKey)
.then((response) => response.json())
.then((responseJson) => {
// set the state of the output here
console.log(responseJson);
setLatestNews(responseJson);
})
.catch((error) => {
console.error(error);
});
}
//using anxios
//asynchronous get request call to fetech latest news
const getDataUsingAnxios = async () => {
//show loading
setLoading(true);
setTimeout(async () => {
//hide loading after the data has been fetched
setLoading(false);
try {
const response = await axios.get(latestNews+ApiKey);
setLatestNews(response.data);
setLoading(false);
console.log(getLatestNews);
} catch (error) {
// handle error
alert(error.message);
}
}, 5000);
};
Data returned when logged from console:
Array [
Object {
"category_id": "8",
"content": "Hi",
"created_at": "2020-11-12T12:43:03.000000Z",
"featured_image": "splash-background_1605184983.jpg",
"id": 19,
"news_url": "doerlife.com",
"title": "I m good how about you",
"updated_at": "2020-11-12T12:43:03.000000Z",
}....]
I now save the data into a state array
const [getLatestNews, setLatestNews] = useState([]);
Here is my swipper(some code ommited - not necessary)
<Swiper
ref={useSwiper}
//cards={categoryID(docs, "2")}
cards={getLatestNews}
cardIndex={0}
backgroundColor="transparent"
stackSize={2}
showSecondCard
cardHorizontalMargin={0}
animateCardOpacity
disableBottomSwipe
renderCard={(card) => <Card card={card} />}
.....
When I try to access any data in the array from my Card reusable component, e.g card.featured_image
I WILL GET THIS ERROR - TypeError: undefined is not an object (evaluating 'card.featured_image').
PLEASE CAN SOMEONE HELP ME.
//Card reusable component for deck swipper
import React from 'react'
import { View, Text, Image, ImageSourcePropType } from 'react-native'
import styles from './Card.styles'
const Card = ({ card }) => (
<View activeOpacity={1} style={styles.card}>
<Image
style={styles.image}
source={card.featured_image}
resizeMode="cover"
/>
<View style={styles.photoDescriptionContainer}>
<Text style={styles.title}>{`${card.title}`}</Text>
<Text style={styles.content}>{`${card.content}`}</Text>
<Text style={styles.details}>
Swipe Left to read news in details
</Text>
</View>
</View>
);
export default Card
I've done something similar to this before so I think I can help a bit. The problem here is that your getLatestNews state has not been updated yet before the cards render. You can fix the problem by having another state called "isDataReturned". Then, have a useEffect that triggers whenever getLatestNews's length changes. If getLatestNews's length is > 0, then you can set isDataReturned to be true and render the deck only when isDataReturned is true.
Here's a code sample that I made:
const [getLatestNews, setLatestNews] = useState([]);
const [dataIsReturned, setDataIsReturned] = useState(false)
useEffect(() => {
const fetchData = async () => {
const result = await axios(
'https://cat-fact.herokuapp.com/facts',
);
setLatestNews(result.data);
};
fetchData();
}, []);
useEffect(() => {
if (getLatestNews.length > 0) {
setDataIsReturned(true)
} else {
setDataIsReturned(false)
}
}, [getLatestNews.length])
if( dataIsReturned === true) {
return (
<View style={styles.container}>
<Swiper
cards={getLatestNews}
renderCard={(card) => {
return (
<View style={styles.card}>
<Text>{card.text}</Text>
</View>
)
}}
onSwiped={(cardIndex) => {console.log(cardIndex)}}
onSwipedAll={() => {console.log('onSwipedAll')}}
cardIndex={0}
backgroundColor={'#4FD0E9'}
stackSize= {3}>
</Swiper>
</View>)
} else {
return(<Text>Loading</Text>)
}
In the renderCard attribute, i changed it from
renderCard={(card) => <Cardz card={card} />}
to
renderCard={(card) => (card && <Cardz card={card} />) || null}
and it worked.

How to use Infinite Scroll with FlatList and React Hook?

I'm refactoring to React Hooks but I can't get Infinite Scroll with FlatList working.
const [page, setPage] = useState(1);
This is my useEffect Hook:
useEffect(() => {
const loadProducts = async () => {
setIsLoading(true);
let response = await fetch(`${api}&page=${page}&perPage=5`);
let results = await response.json();
setProducts([...products, ...results.data]);
setIsLoading(false);
};
loadProducts();
}, [page]);
Offset is ${page}, limit is &perPage=5 (hardcoded to 5)
Flatlist:
<FlatList
data={products}
keyExtractor={(item) => item.id}
renderItem={renderGridItem}
onEndReached={loadMore}
onEndThreshold={0.3}
/>;
LoadMore:
const loadMore = () => {
setPage(page + 1);
};
In theory, this should work, shouldn't it?
Description
I was struggling a lot with this myself. Here's an example using a SectionList (basically the same as a Flatlist)
The header numbers indicates the request number send to the API. You can check that the request are in the correct order and that there are no duplicates, by clicking the "Check Numbers" button.
In this example we use reqres.in to simulate a fetch to some data.
The example also implements pull-to-refresh. Again, you can check that the length of the array is as expected after a pull-to-refresh by clicking the "Check length" button.
Expo snack
A snack of the example can be found here: https://snack.expo.io/BydyF9yRH
Make sure to change platform to iOS or Android in the snack (Web will not work)
Code
import * as React from 'react';
import { ActivityIndicator } from 'react-native'
var _ = require('lodash')
import {
StyleSheet,
Text,
View,
SafeAreaView,
SectionList,
Button,
RefreshControl
} from 'react-native';
function Item(item) {
return (
<View style={styles.item}>
<Text style={styles.title}>{item.title.first_name}</Text>
</View>
);
}
export default function testSectionList({ navigation }) {
const [data, setData] = React.useState()
const [loading, setLoading] = React.useState(true)
const [refreshing, setRefreshing] = React.useState(false);
const [showRefreshingIndicator, setShowRefreshingIndicator] = React.useState(false);
const dataIndex = React.useRef(0);
const totalHits = React.useRef(42); // In real example: Update this with first result from api
const fetchData = async (reset: boolean) => {
if (reset === true) dataIndex.current = 0;
// Make sure to return if no more data from API
if (dataIndex.current !== 0 && dataIndex.current >= totalHits.current) return []
// For example usage, select a random page
const fakepage = Math.round(Math.random()) * 2
const resultObject = await fetch(`https://reqres.in/api/users?page=${fakepage}`);
const result = await resultObject.json()
dataIndex.current++;
return {
title: `${dataIndex.current-1}`,
data: await result.data
}
}
const count = () => {
alert(data.length)
}
const checkPageNumbers = () => {
const numbers = data.map(item => parseInt(item.title))
const incremental = [...Array(data.length).keys()]
alert(_.isEqual(numbers, incremental))
}
const getInitialData = async () => {
const list = await fetchData(false)
if(!list) return
setData([list])
setLoading(false)
}
React.useEffect(() => {
getInitialData()
}, [])
const onEndReached = async () => {
const newItems = await fetchData(false)
if(!newItems.data.length) return
setData([...data, newItems])
}
const onRefresh = React.useCallback(async () => {
setShowRefreshingIndicator(true);
const newItems = await fetchData(true)
setData([newItems])
setShowRefreshingIndicator(false)
}, [refreshing]);
if (loading) return <Text>Loading...</Text>
return (
<SafeAreaView style={styles.container}>
<Button title={"Check numbers"} onPress={() => checkPageNumbers()} />
<Button title={"Check length"} onPress={() => count()} />
<SectionList
sections={data}
refreshing={refreshing}
refreshControl={
<RefreshControl refreshing={showRefreshingIndicator} onRefresh={onRefresh} />
}
onEndReached={() => {
if(refreshing) return;
setRefreshing(true)
onEndReached().then(() => {
setRefreshing(false)
})
}}
onEndReachedThreshold={1}
keyExtractor={(item, index) => item + index}
renderItem={({ item }) => <Item title={item} />}
renderSectionHeader={({ section: { title } }) => (
<Text style={styles.header}>{title}</Text>
)}
ListFooterComponent={<ActivityIndicator size={"large"} />}
stickySectionHeadersEnabled={false}
/>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
marginTop: 40,
marginHorizontal: 16,
},
item: {
backgroundColor: '#f9c2ff',
padding: 2,
marginVertical: 2,
},
header: {
fontSize: 16,
},
title: {
fontSize: 12,
},
});
Try to use useCallback instead of useEffect on this case. Also, I've shown you how you can prevent spreading null result to setState.
const loadProducts = async () => {
setIsLoading(true);
let response = await fetch(`${api}&page=${page}&perPage=5`);
let results = await response.json();
if (result.data) {
setProducts([...products, ...results.data]);
}
setIsLoading(false);
};
useEffect(() => {
loadProducts();
}, [])
const onLoadMore = useCallback(() => {
loadProducts();
}
for more information about useCallback, please read this. https://reactjs.org/docs/hooks-reference.html#usecallback

Resources