How to get specific data from api with condition - reactjs

Hello so i tried to make an website using Goole Books API. I want to get the listPrice from the object, but theres some of the book that doesnt have the listPrice in them. So for the example in object number 1 there is some code called saleability: "NOT_FOR_SALE" meanwhile object number 2 have and saleability: "FOR_SALE". If i tried to map the data, there is a error Uncaught TypeError: i.saleInfo.saleability.listPrice is undefined. How do you make spesific condition for this problem.
This is the code :
const CardBooks = (props) => {
const url = "https://www.googleapis.com/books/v1/volumes?q=:keyes&key=AIzaSyDIwDev4gFHRqCh4SSaO9eLKEeI7oYt6aE&maxResults=27"
const result = "&maxResults=40"
const [bookHome, setBookHome] = useState([]);
const [modalShow, setModalShow] = React.useState(false);
useEffect( () => {
axios
.get(`${url}`)
.then( (res) => {
setBookHome(res?.data?.items)
console.log(res?.data?.items)
})
.catch(console.error);
}, [])
return (
<div>
<Container fluid className='wrapper'>
{bookHome && bookHome.map((i, index) => {
return(
<div className='image-container' key={index}>
<div className="book read">
<div className="cover">
<img src={i.volumeInfo.imageLinks.thumbnail} />
</div>
<div className="info">
<h3 className="title">{i.volumeInfo.title}</h3>
</div>
<Example
thumbnail={i.volumeInfo.imageLinks.thumbnail}
title={i.volumeInfo.title}
description={i.volumeInfo.description}
category={i.volumeInfo.categories}
page={i.volumeInfo.pageCount}
language={i.volumeInfo.language}
publisher={i.volumeInfo.publisher}
published={i.volumeInfo.publishedDate}
link={i.volumeInfo.previewLink}
epub={i.accessInfo.epub.isAvailable}
currency={i.saleInfo.saleability.listPrice.currencyCode}
price={i.saleInfo.saleability.listPrice.amount}
/>
</div>
</div>
)
})}
</Container>
</div>
)
}
export default CardBooks

Basically you just need a null/undefined check, a quick and dirty solution:
currency={i.saleInfo.saleability.listPrice ? i.saleInfo.saleability.listPrice.currencyCode : ''}
It's better to use conditional rendering and/or passing the whole object to the component and handling it inside.

Related

Only one prop is able to pass the data

When I try to pass props from one component to another, only one of them receives data. I am not able to get "users" details to AccountabilityChart component.
I am only able to get data props but not users props. What is the issue? I am not able to figure it out the behaviour. Can any one explain why this happens and if their is a way to have both the props
const Card = (props: any) => {
useEffect(()=>{
console.log("These are props: ", props);
}, []);
const [selectedOption, setSelectedOption] = useState(null);
return (
<ul>
{props.data.map((item: any, index: any) => (
<React.Fragment key={item.name}>
<li>
<div className="card">
<div className="card-body">
<p>{item.name}</p>
</div>
<div>
<Select
defaultValue={selectedOption}
onChange={handleChange}
className="select"
options={props.users}
/>
</div>
<div></div>
</div>
{item.children?.length && <Card data={item.children} users={[]} />}
</li>
</React.Fragment>
))}
</ul>
);
};
const AccountabilityChartComponent = () => {
const [hierarchy, setHierarchy] = useState([]);
const [users, setUsers] = useState([]);
useEffect(() => {
someApiCall.then(
function success(results) {
setHierarchy(results.entities);
}
);
someApiCall.then(
function success(results: any) {
setUsers(wrapped);
}
);
}, []);
return (
<div className="grid">
<div className="org-tree">
<Card
users={users}
data={hierarchy}
/>
</div>
</div>
);
};
export default AccountabilityChartComponent;
Organizational hierarchy I am trying to create
Console log is called 4 times. It means the state is updated 4 times? Will it effect my performance?

Can React Testing Library find element after state change?

I'm absolutely new to react testing, learning from a playlist in youtube.
In this very moment of the tutorial, the instructor tests a component, which:
has a useEffect.
inside a useEffect, there is an axios.get
state is updated with api response.
state data is turned to elements (each has data-testid attribute equal to follower-item-{someNumber}.
The objective of the test is to find a data element.
. despite that i'm almost coping his code, his test passes, but mine doesn't. seems like the test runs before data fetching.
the component:
export default function FollowersList() {
const [followers, setFollowers] = useState([]);
useEffect(() => {
fetchFollowers();
}, []);
const fetchFollowers = async () => {
const { data } = await axios.get("https://randomuser.me/api/?results=5");
setFollowers(data.results);
};
return (
<div className="followerslist-container">
<div>
{followers.map((follower, ind) => (
<div className="follower-item" data-testid={`follower-item-${ind}`}>
<img src={follower.picture.large} />
<div className="followers-details">
<div className="follower-item-name">
<h4>{follower.name.first}</h4> <h4>{follower.name.last}</h4>
</div>
<p>{follower.login.username}</p>
</div>
</div>
))}
</div>
<div className="todo-footer">
<Link to="/">Go Back</Link>
</div>
</div>
);
}
The test:
const MockFollowersList = () => {
return (
<BrowserRouter>
<FollowersList />
</BrowserRouter>
);
};
describe("followers list", () => {
it("should render follower items", async () => {
render(<MockFollowersList />);
let el = await screen.findByTestId("follower-item-0");
expect(el).toBeInTheDocument();
});
});
result:
Unable to find an element by: [data-testid="follower-item-0"]
<body>
<div>
<div
class="followerslist-container"
>
<div />
<div
class="todo-footer"
>
<a
href="/"
>
Go Back
</a>
</div>
</div>
</div>
</body>
The problem is that you don't mock data. Try using https://www.npmjs.com/package/nock#debugging or something similar.
I recommend you to install and use eslint rule for testing library: https://github.com/testing-library/eslint-plugin-testing-library. This rule could help you avoid a common mistakes.

Dynamically rendering child components in react

I'm using firestore database to store my data in the collection "listings". So for each document in "listings", I need to render a <BookListing/> element in Home.js with the data from each document. From my research, there are a few other questions similar to this one out there, but they're outdated and use different react syntax. Here's my code:
function BookListing({id, ISBN, title, image, price}) {
return (
<div className="bookListing">
<div className='bookListing_info'>
<p className="bookListing_infoTitle">{title}</p>
<p className="bookListing_infoISBN"><span className="bookListing_infoISBNtag">ISBN: </span>{ISBN}</p>
<p className="bookListing_infoPrice">
<small>$</small>
{price}
</p>
</div>
<img className="bookListing_img" src={image} alt=""></img>
<button className="bookListing_addToCart">Add to Cart</button>
</div>
)
}
export default BookListing
function Home() {
document.title ="Home";
useEffect(() => {
getDocs(collection(db, 'listings'))
.then(queryCollection => {
queryCollection.forEach((doc) => {
console.log(doc.id, " => ", doc.data());
const element = <BookListing id="456" ISBN="0101" title="sample_title" image="https://nnpbeta.wustl.edu/img/bookCovers/genericBookCover.jpg" price="25"/>;
ReactDOM.render(
element,
document.getElementById('home-contents-main')
);
})
});
}, []);
return (
<div className="home">
<div className="home_container">
<div id="home-contents-main" className="home_contents">
</div>
</div>
</div>
)
}
export default Home
It's best (and most common) to separate the task into two: asynchronously fetching data (in your case from firestore), and mapping that data to React components which are to be displayed on the screen.
An example:
function Home() {
// A list of objects, each with `id` and `data` fields.
const [listings, setListings] = useState([]) // [] is the initial data.
// 1. Fetching the data
useEffect(() => {
getDocs(collection(db, 'listings'))
.then(queryCollection => {
const docs = [];
queryCollection.forEach((doc) => {
docs.push({
id: doc.id,
data: doc.data()
});
// Update the listings with the new data; this triggers a re-render
setListings(docs);
});
});
}, []);
// 2. Rendering the data
return (
<div className="home">
<div className="home_container">
<div className="home_contents">
{
listings.map(listing => (
<BookListing
id={listing.id}
ISBN={listing.data.ISBN}
title={listing.data.title}
image={listing.data.image}
price={listing.data.price}
/>
))
}
</div>
</div>
</div>
);
}
Some tips:
Fetching data from other web servers or services can be, and typically is, done in the same manner.
This example could be improved a lot in terms of elegance with modern JS syntax, I was trying to keep it simple.
In most cases, you don't want to use ReactDOM directly (only for the entry point of your app), or mess with the DOM manually; React handles this for you!
If you're not familiar with the useState hook, read Using the State Hook on React's documentation. It's important!
You can create a reusable component, and pass the data to it, and iterate over it using map() . define a state, and use it within the useEffect instead of creating elements and handling the process with the state as a data prop.
function BookListing({ id, ISBN, title, image, price }) {
return (
<div className="bookListing">
<div className="bookListing_info">
<p className="bookListing_infoTitle">{title}</p>
<p className="bookListing_infoISBN">
<span className="bookListing_infoISBNtag">ISBN: </span>
{ISBN}
</p>
<p className="bookListing_infoPrice">
<small>$</small>
{price}
</p>
</div>
<img className="bookListing_img" src={image} alt=""></img>
<button className="bookListing_addToCart">Add to Cart</button>
</div>
);
}
function Home() {
const [data, setData] = useState([]);
useEffect(() => {
document.title = 'College Reseller';
getDocs(collection(db, 'listings')).then((queryCollection) => setData(queryCollection));
}, []);
return (
<div className="home">
<div className="home_container">
<div id="home-contents-main" className="home_contents">
{data.map((doc) => (
<BookListing
id="456"
ISBN="0101"
title="sample_title"
image="https://nnpbeta.wustl.edu/img/bookCovers/genericBookCover.jpg"
price="25"
/>
))}
</div>
</div>
</div>
);
}
export default Home;

An error comes when mapping array of objects in React

I try to get data from the backend and display that data in the frontend. This is the code that I wrote to do this task.
function ViewPost() {
const { id } = useParams();
console.log(id);
const [posts, setPosts] = useState({});
useEffect(()=>{
getOnePost();
}, []);
useEffect(()=>{
if (posts && posts.location) {
console.log(posts.location);
console.log(posts.location.longitude);
console.log(posts.location.latitude);
}
}, [posts]);
const getOnePost = async () => {
try {
const response = await axios.get(`/buyerGetOnePost/${id}`)
console.log(response);
const allPost=response.data.onePost;
setPosts(allPost);
} catch (error) {
console.error(`Error: ${error}`)
}
}
console.log(posts);
console.log(posts.wasteItemList);
return(
<div className="posts-b">
<div className="posts__container-b">
<h1>Post Details</h1>
<main className="grid-b">
{posts.wasteItemList.map((notes,index)=>(
<article>
<div className="text-b">
<h3>Post ID: {index+1}</h3>
<p>Waste Item: Polythene Roll</p>
<p>Quantity: 1 kg</p>
</div>
</article>
))}
</main>
</div>
</div>
);
}
export default ViewPost;
When I console.log(posts) it shows post data successfully. The wasteItemList is an array it has two objects.
When I console.log(posts.wasteItemList) it also shows the two array of objects successfully.
But the problems comes when I try to map wasteItemList. I tried to map using thisposts.wasteItemList.map. But I get an error 'TypeError: Cannot read property 'map' of undefined'. How do I solve this issue?
This happens because the data is not available immediately so, for a while posts.wasteItemList is undefined.
Whenever accessing wasteItemList you should use
posts && posts.wasteItemList && posts.wasteItemList.map(()=> *)
This gives you the complete safety.
If you are using TypeScript or node version >= 14, then you can use optional chaining.
posts?.wasteItemList?.map(()=>{})
Which is basically the same thing just more syntactically pleasing.
Change this particular line
{posts.wasteItemList.map((notes,index)=>
to
{posts.wasteItemList && posts.wasteItemList.map((notes,index)=>
The initial value of posts is {}, so there is no wasteItemList property on posts.
Attempting to access posts.wasteItemList would return undefined and you cannot map over undefined.
Try using optional chaining on posts?.wasteItemList?.map
return(
<div className="posts-b">
<div className="posts__container-b">
<h1>Post Details</h1>
<main className="grid-b">
{posts?.wasteItemList?.map((notes,index)=>(
<article>
<div className="text-b">
<h3>Post ID: {index+1}</h3>
<p>Waste Item: Polythene Roll</p>
<p>Quantity: 1 kg</p>
</div>
</article>
))}
</main>
</div>
</div>
);

Why does React tell me unexpected token "."

I'm trying to display an json array on the screen but react tells me unexpected token "." I have searched around for 3 hours now but I can't figure out what is wrong or how to fix this. The other parts of the detail object all display correctly but for some reason the array just doesn't want to. I hope someone can help me with this problem.
the exact error I get is:
and the json in console log.
below is my code for the component.
function GeneDetail({ match }) {
useEffect(() => {
fetchDetails();
}, []);
const [detail, setDetail] = useState({})
//const [alleles, setAlleles] = useState([])
const fetchDetails = async () => {
const fetchDetails = await fetch(
'/api/get_genedetail?g='+match.params.genename+''
);
const detail = await fetchDetails.json()
setDetail(detail)
//setAlleles(detail.alleles)
}
console.log('alleles', detail.alleles)
return(
<div className="main-content">
<Container maxWidth="lg">
<div className="grid-container">
<div className="grid-title">
<h2>Gene: <i>{detail.geneName}</i></h2>
</div>
<div className="grid-subtitle">
<h3>Type: {detail.segmentFullName}</h3>
</div>
<div className="grid-alleles">
test
{detail.alleles ?
{detail.alleles.map(function (allele, i) {
return <div key={i}>
<h5>{allele.Number}</h5>
</div>
})}
: (<p>"No alleles found."</p>)}
</div>
</div>
</Container>
</div>
);
}
React errors can be confusing, the problem here is not that you have a dot there. Instead, you declare a variable expression in a variable expression, essentially like this:
{condition?{mappedData}:(alternative)}
You cannot declare an expression in an expression, you should've written it like this:
{detail.alleles ?
detail.alleles.map(function (allele, i) {
return <div key={i}>
<h5>{allele.Number}</h5>
</div>
})
: (<p>No alleles found.</p>)}
UpVote, if the solution works
function GeneDetail({ match }) {
useEffect(() => {
fetchDetails();
}, []);
const [detail, setDetail] = useState({})
//const [alleles, setAlleles] = useState([])
const fetchDetails = async () => {
const fetchDetails = await fetch(
'/api/get_genedetail?g='+match.params.genename+''
);
const detail = await fetchDetails.json()
setAlleles(detail.alleles)
}
console.log('alleles', detail.alleles)
return(
<div className="main-content">
<Container maxWidth="lg">
<div className="grid-container">
<div className="grid-title">
<h2>Gene: <i>{detail.geneName}</i></h2>
</div>
<div className="grid-subtitle">
<h3>Type: {detail.segmentFullName}</h3>
</div>
<div className="grid-alleles">
test
{alleles.length >= 0 ?
{alleles.map( (allele, i) => {
return <div key={i}>
<h5>{allele.Number}</h5>
</div>
})}
: (<p>"No alleles found."</p>)}
</div>
</div>
</Container>
</div>
);
}

Resources