React api calling and create same div by 'map' method - reactjs

I want to call api and generate div using data from api, but I don't know why this code is not working. It doesn't show anything on the page.
This is my code. countryArray is an object array, and it has property of population, name, continent, capital.
import React from 'react'
function Countries() {
fetch("https://restcountries.com/v3.1/all")
.then((response)=>response.json())
.then((countryArray)=>{
return (
<div>
{countryArray.map((country)=>(
<div className="Country_wrapper">
<div className="Flag_wrapper">
</div>
<div className="Explanation_wrapper">
<h2>{country.name}</h2>
<p>Population: {country.population}</p>
<p>Region: {country.continents}</p>
<p>Capital: {country.capital}</p>
</div>
</div>
))}
</div>
)
},
(err)=>{
console.log(err);
})
}
export default Countries

Hello there first of all you need save the api data in a state and then fetch the api in useEffect then you can use the api data in your react app
import React , {useState , useEffect} from 'react';
function app() {
const [examples , setExamples] = useState([]);
useEffect(() => {
fetch('https://restcountries.com/v3.1/all')
.then((res) => res.json())
.then((data) => {
setExamples(data);
})
.catch((err) => console.log(err));
},[]);
return(
<>
<div>
{
examples.map((example) => (
<div className="Country_wrapper">
<div className="Flag_wrapper">
</div>
<div className="Explanation_wrapper">
<h2>{example.name.official}</h2>
<p>Population: {example.population}</p>
<p>Region: {example.continents}</p>
<p>Capital: {example.capital}</p>
</div>
</div>
))
}
</div>
</>
);
}
export default app
this code is working

You need to return a jsx element. The usual way of doing data fetching inside react component is to do it inside an effect.
A minimal example would be like this.
function Countries() {
const [countryArray, setCountryArray] = useState([]);
useEffect(() => {
(async function () {
const res = await fetch("https://restcountries.com/v3.1/all");
const json = await res.json();
setCountryArray(json)
})()
}, [])
return (
<div>
{countryArray.map((country)=>(
<div className="Country_wrapper">
<div className="Flag_wrapper">
</div>
<div className="Explanation_wrapper">
<h2>{country.name.common}</h2>
<p>Population: {country.population}</p>
<p>Region: {country.continents}</p>
<p>Capital: {country.capital}</p>
</div>
</div>
))}
</div>
)
}
Ofc you should also take care of race conditions, errors, loading states, or use a library that does all this stuff for you and more like react query.
Check the documentation for more information, fetching data

You can't return jsx from fetch, that won't be rendered.
Use useState inside a useEffect to save the data, then return from the functinon itself
const {useState, useEffect} = React;
function Countries() {
const [ data, setData ] = useState([])
useEffect(() => {
function getData() {
fetch("https://restcountries.com/v3.1/all")
.then((response) => response.json())
.then((countryArray) => setData(countryArray)
);
};
getData();
}, [ ]);
return (
<div>
{data.map((country)=>(
<div className="Country_wrapper">
<div className="Flag_wrapper">
</div>
<div className="Explanation_wrapper">
<h2>{country.name.common}</h2>
<p>Population: {country.population}</p>
<p>Region: {country.continents}</p>
<p>Capital: {country.capital}</p>
</div>
</div>
))}
</div>
)
}
ReactDOM.render(<Countries />, document.getElementById("react"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Demo takes quite some time to load, so here's a pic:

Related

React Hooks: Can't perform a React state update on a component that hasn't mounted yet

Edit 1
I have updated my code (changed from useState to useEffect) but seems like the same problem. Only if I remove the return code, it runs perfectly. Seems like it happens when I have lots of data to show in view
Edit 2
I updated my useEffect() to below but still the same problem.
useEffect(() => {
let mounted = true;
if (mounted) {
FetchOrderDetails();
}
return () => (mounted = false);
}, []);
Edit Ends
I get this warning sometimes and it crashes Warning: Can't perform a React state update on a component that hasn't mounted yet. This indicates that you have a side-effect in your render function that asynchronously later calls tries to update the component. Move this work to useEffect instead.
Surprisingly, sometimes the code runs perfectly which most of the time it doesn't. What is wrong?
My code
import React, { useState } from "react";
import { useParams } from "react-router-dom";
import { useCookies } from "react-cookie";
import * as Constants from "../Constants";
import HeaderTemplate from "../Templates/HeaderTemplate";
const OrderDetails = () => {
const { transactionRefNo, transactionId } = useParams();
const [cookies] = useCookies(["user"]);
const [orderDetails, setOrderDetails] = useState([]);
const FetchOrderDetails = async () => {
let options = {
employeeid: parseInt(cookies.employeeId),
transactionid: parseInt(transactionId),
};
await fetch(Constants.API_ENDPOINT + "/test/orderdetails", {
method: "POST",
body: JSON.stringify(options),
})
.then((response) => response.json())
.then((data) => setOrderDetails(data.order));
};
useEffect(() => {
console.log(transactionRefNo, transactionId);
FetchOrderDetails();
}, []);
return (
<div>
<HeaderTemplate />
<React.Fragment>
{/* Order Details Starts*/}
<div className="panel panel-default mt-4">
<div className="panel-heading">
<h6 className="panel-title">Order Details</h6>
</div>
<div className="panel-body">
<b>
<small>
{"Order #" +
orderDetails["transaction_reference_no"]}
</small>
</b>
<div className="row">
<div className="col-md-6">
<small>Port: {orderDetails["port"]}</small>
</div>
</div>
<div className="row">
<div className="col-md-6">
<small>
Type:{" "}
{orderDetails["transaction_type"] ===
"selling_stages"
? "Selling"
: "Buying"}
</small>
</div>
</div>
</div>
</div>
{/* Order Details Ends*/}
</React.Fragment>
</div>
);
};
export default OrderDetails;
It turns out, useEffect tries to update the state both when it mounts and unmounts. Hence the error occurs. As suggested by #super, I used swr to circumvent the issue in my case
import React from "react";
import { useParams } from "react-router-dom";
import { useCookies } from "react-cookie";
import * as Constants from "../Constants";
import HeaderTemplate from "../Templates/HeaderTemplate";
const fetcher = async (cookies, transactionId) => {
let options = {
employeeid: parseInt(cookies.employeeId),
transactionid: parseInt(transactionId),
};
const res = await fetch(Constants.API_ENDPOINT + "/test/orderdetails", {
method: "POST",
body: JSON.stringify(options),
});
const json = await res.json();
return json;
};
const OrderDetails = () => {
const { transactionRefNo, transactionId } = useParams();
const [cookies] = useCookies(["user"]);
const { data, error } = useSwr([cookies, transactionId], fetcher);
if (!data) return <div>Loading...</div>;
if (error) return <div>Error</div>;
return (
<div>
<HeaderTemplate />
<React.Fragment>
{/* Order Details Starts*/}
<div className="panel panel-default mt-4">
<div className="panel-heading">
<h6 className="panel-title">Order Details</h6>
</div>
<div className="panel-body">
<b>
<small>
{"Order #" +
data.order.transaction_reference_no}
</small>
</b>
<div className="row">
<div className="col-md-6">
<small>Port: {data.order.port}</small>
</div>
</div>
<div className="row">
<div className="col-md-6">
<small>
Type:{" "}
{data.order.transaction_type ===
"selling_stages"
? "Selling"
: "Buying"}
</small>
</div>
</div>
</div>
</div>
{/* Order Details Ends*/}
</React.Fragment>
</div>
);
};
export default OrderDetails;

copying to clipboard react

I'm trying to copy a text to clipboard using a button, the code working fine but the problem is copying another post text not the targeted one! I have multi posts using map. and each post has a text and a button, the issue now is when i'm hitting the button it copies the first post text!
The code for Post.jsx
export default function Post({ post }) {
var cpnBtn = document.getElementById("cpnBtn");
var cpnCode = document.getElementById("cpnCode");
const CopyCode = () => {
navigator.clipboard.writeText(cpnCode.innerHTML);
cpnBtn.innerHTML = "COPIED";
setTimeout(function(){
cpnBtn.innerHTML = "COPY CODE";
}, 3000);
}
return (
<div className="coupon">
<div className="coupon-container">
<div className="coupon-card">
<div className="postInfo">
{post.photo && <img className="postImg" src={post.photo} alt="" />}
<div className="postTitle">
<span >{post.shop}</span>
</div>
<div className="postTitle">
<span >{post.title}</span>
</div>
<div className="coupon-row">
<span id="cpnCode">{post.coupon}</span>
<button id="cpnBtn" onClick={CopyCode}>COPY CODE</button>
</div>
</div>
</div>
</div>
</div>
);
}
The code for Posts.jsx
export default function Posts({ posts }) {
return (
<div className="posts">
{posts.map((p) => (
<Post post={p} />
))}
</div>
);
}
The reason why it's copying always the first coupon is that you're rendering multiple posts and all have a coupon with id="cpnCode" so it will always use the top one. Besides that, I've explained below the proper way to implement such functionality in react.
The easiest way to solve this would be to use the useState of react to hold the text of the button in it. When using react we try to avoid mutating DOM elements manually instead we rely on to react to perform these updates.
Also, there's no need to take the coupon value from the coupon HTML element if you already have access to the post.
writeText is a Promise that's why I added the then block and the catch so you can see the error in your console in case something goes wrong.
import { useState } from "react";
export default function Post({ post }) {
const [bttnText, setBttnText] = useState("COPY CODE");
const copyCode = () => {
navigator.clipboard
.writeText(post.coupon)
.then(() => {
setBttnText("COPIED");
setTimeout(function () {
setBttnText("COPY CODE");
}, 3000);
})
.catch((err) => {
console.log(err.message);
});
};
return (
<div className="coupon">
<div className="coupon-container">
<div className="coupon-card">
<div className="postInfo">
{post.photo && <img className="postImg" src={post.photo} alt="" />}
<div className="postTitle">
<span>{post.shop}</span>
</div>
<div className="postTitle">
<span>{post.title}</span>
</div>
<div className="coupon-row">
<span>{post.coupon}</span>
<button onClick={copyCode}>{bttnText}</button>
</div>
</div>
</div>
</div>
</div>
);
}
Alterantively in case you really need to access the htmlElements it can be done with useRef e.g
import { useState, useRef } from "react";
export default function Post({ post }) {
const [bttnText, setBttnText] = useState("COPY CODE");
const couponRef = useRef();
const copyCode = () => {
// just a safety mechanism to make sure that
// the references found the DOM elmenets button and coupon
// before trying to use them
if (!couponRef.current) return;
navigator.clipboard
// again doesn't make sense to use here
// couponRef.current.innerHTML
// since you got access to post.coupon
.writeText(couponRef.current.innerHTML)
.then(() => {
setBttnText("COPIED");
setTimeout(function () {
setBttnText("COPY CODE");
}, 3000);
})
.catch((err) => {
console.log(err.message);
});
};
return (
<div className="coupon">
<div className="coupon-container">
<div className="coupon-card">
<div className="postInfo">
{post.photo && <img className="postImg" src={post.photo} alt="" />}
<div className="postTitle">
<span>{post.shop}</span>
</div>
<div className="postTitle">
<span>{post.title}</span>
</div>
<div className="coupon-row">
<span ref={couponRef}>{post.coupon}</span>
<button onClick={copyCode}>{bttnText}</button>
</div>
</div>
</div>
</div>
</div>
);
}

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;

Why is my state's value is always undefined?

I've read all the question asked that similar to my problem , but it still cant's solve my issues . I'm fetching datas from an api and assign it's values into my state , the program compiled successfully but this message appears in the browser "TypeError: weatherData.main is undefined"
Here's my code
import './App.css';
import React,{useEffect,useState} from 'react';
function App() {
const [weatherData,setWeatherData] = useState({});
const [position,setPosistion] = useState({});
useEffect(()=>{
navigator.geolocation.getCurrentPosition( pos => {
setPosistion(()=>{
return {
latitude : pos.coords.latitude,
longitude : pos.coords.longitude
}
})
});
},[])
useEffect(()=>{
fetch(`https://api.openweathermap.org/data/2.5/weather?lat=${position.latitude}&lon=${position.longitude}&units=metric&appid=a*****5`)
.then( res => res.json() )
.then( resJson => {
setWeatherData(()=>resJson)
})
},[position]);
return (
<div className="App">
<Weather weatherData={weatherData} />
</div>
);
}
const Weather = ({weatherData}) => {
return(
<React.Fragment>
<div className="location-time">
<span id="location">{weatherData.name}</span>
</div>
<div className="weather">
<span className="temp">{`${weatherData.main.temp} C`}</span>
<div className="icon"></div>
<div className="description">{weatherData.weather[0].main}</div>
<div className="low-max">{`max : ${weatherData.main.temp_max} min : ${weatherData.main.temp_min}`}</div>
<div className="feels-like">{`feels like : ${weatherData.main.feels_like}`}</div>
<button >REFRESH</button>
</div>
</React.Fragment>
)
}
export default App;
the state contains
{"coord":{"lon":106.8451,"lat":-6.2146},"weather":[{"id":802,"main":"Clouds","description":"scattered clouds","icon":"03d"}],"base":"stations","main":{"temp":302.76,"feels_like":307.06,"temp_min":300.15,"temp_max":305.93,"pressure":1007,"humidity":74},"visibility":6000,"wind":{"speed":2.57,"deg":340},"clouds":{"all":40},"dt":1613038370,"sys":{"type":1,"id":9383,"country":"ID","sunrise":1612997835,"sunset":1613042198},"timezone":25200,"id":1642911,"name":"Jakarta","cod":200}
Before the data is fetched, weather is initialised to be a empty object and hence weather.main is undefined.
You should consider rendering a fallback page till weather data is fetched
const Weather = ({weatherData}) => {
if(Object.keys(weatherData).length === 0) {
return <div>{/* Somee info here or maybe a loader*/}</div>
}
return(
<React.Fragment>
<div className="location-time">
<span id="location">{weatherData.name}</span>
</div>
<div className="weather">
<span className="temp">{`${weatherData.main.temp} C`}</span>
<div className="icon"></div>
<div className="description">{weatherData.weather[0].main}</div>
<div className="low-max">{`max : ${weatherData.main.temp_max} min : ${weatherData.main.temp_min}`}</div>
<div className="feels-like">{`feels like : ${weatherData.main.feels_like}`}</div>
<button >REFRESH</button>
</div>
</React.Fragment>
)
}

Resources