How can I display the data of an array from an API? - reactjs

i'm having a problem displaying the data from the punkbeer API and would appreciate any kind of help
import { useEffect, useState, Fragment } from 'react';
import { Switch, Link, Route, BrowserRouter as Router } from 'react-router-dom';
import React from 'react'
import '../Drink/drinks.css';
function Drinks() {
const [beer, setBeer] = useState('[]');
const [selectedBeer, setSelectedBeer] = useState("")
const fetchDrinks = function() {
fetch(`https://api.punkapi.com/v2/beers`)
.then( function(result) {
return result.json()
})
.then(function (data) {
setBeer(data)
data.forEach(beer => {
<div>
<div className="drink-img">
<img src="{beer.image_url}"/>
</div>
<div className="drink-title">
<h2>{beer.name}</h2> </div>
<div className="drink-tagline">
<h2>{beer.tagline}</h2> </div>
<div className="drink-food">
<h2>{beer.food_pairing}</h2> </div>
</div>
});
})
.catch(function () {
});
}
useEffect( function(){
fetchDrinks();
}, []);
return (
<div className="drink-container">
<div className="next">
<p>After you are picked your drink, click next to place your order </p>
<Link to="./Order">
<button className="btn" >Order</button>
</Link>
</div>
</div>
)
}
export default Drinks

You can save the return in variable/state.
Example you save in variabe that have name "data"
in view, you can add this
{data.map((value,index)=>{
return(
<div key={index}>
<div className="drink-img">
<img src="{value.image_url}"/>
</div>
<div className="drink-title">
<h2>{value.name}</h2> </div>
<div className="drink-tagline">
<h2>{value.tagline}</h2> </div>
<div className="drink-food">
<h2>{value.food_pairing}</h2> </div>
</div>
)}
)}

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>
);
}

How to render input information and send it to my api's fetch?

I'm having a problem, which is to bring the weather information of a city where the user typed in the input, to my component.
I managed to make it so that when the user typed the city or country, it was already entered as a parameter in my api, but the weather information only appears when I CTRL+S my tsx file.
The same follows in the codes and images below
CityWeatherSearch.tsx
import { MagnifyingGlass } from 'phosphor-react'
import { FormEvent, useRef, useState } from 'react';
import * as Styled from './style'
interface CityPropsP{
city:string,
setCity: typeof useState
}
export function CityWeatherSearch({city,setCity}:CityPropsP){
const inputRef = useRef<HTMLInputElement>(null);
function handleClick(event:FormEvent) {
event.preventDefault();
const inputCity = inputRef?.current?.value;
setCity(inputCity)
}
return(
<>
<Styled.BoxSearchCity>
<div className="headerSearch">
<form onSubmit={handleClick}>
<input type="text" placeholder='Procurar Cidade...' ref={inputRef} />
<button type="submit">
<MagnifyingGlass/>
</button>
</form>
</div>
<div className="bodySearch">
</div>
</Styled.BoxSearchCity>
</>
)
}
MainWeatherLive.tsx
import {Clock} from 'phosphor-react'
import { useState } from 'react'
import { useFetch } from '../../GetData/useFetch'
import * as Styled from './style'
type DataWeather = {
name: string,
condition:{
text:string,
icon:string
},
temp_c:number,
hour:[{
temp_c:number,
time:string,
condition:{
text:string,
icon:string
}
}]
}
interface CityPropsMain{
city:string,
}
export function MainWeatherLive({city}: CityPropsMain){
const {dataCurrent:dataCurrentApi, dataForecast:forecastApi}
= useFetch<DataWeather>(`/v1/forecast.json?key=aff6fe0e7f5d4f3fa0611008221406&q=${city}?days=1&aqi=no&alerts=no`);
console.log(city)
return(
<>
<Styled.HeaderBox>
<h6>Weather Now</h6>
</Styled.HeaderBox>
<Styled.Container>
{city == '' &&
<p>Carregando...</p>
}
<div className="mainInformation">
<div className="temperatura">
<span>{dataCurrentApi?.temp_c}º</span>
</div>
<div>
</div>
<div className="boxCidade">
<div className="cidade">
<span>{city}</span>
</div>
<div className="tempoHoras">
<span>
{new Date().toLocaleTimeString('pt-BR',{hour12:false, hour:'numeric',minute:'numeric'})} - {new Date().toLocaleDateString()}
</span>
</div>
</div>
<div className="iconeTem">
<img src={dataCurrentApi?.condition.icon} alt={dataCurrentApi?.condition.text} />
</div>
</div>
<div className="footerBox">
<div className="headerFooter">
<Clock/>
<span>Horários</span>
</div>
<div className="listaHorarios">
<ul className="boxTT">
{
forecastApi?.hour?.map(weatherA =>{
const hourTemp = weatherA.time.split(" ")[1].replace(":00","");
const hourTempNumber:number = +hourTemp;
const hourNow = new Date().getHours();
return(
<>
{
hourTempNumber == hourNow &&
<li>
<div className="titulo" key={weatherA.temp_c}>
<span>{hourTempNumber}</span>
</div>
<div className="temperatura">
<img src={weatherA.condition.icon} alt={weatherA.condition.text} />
<span>{dataCurrentApi?.temp_c}º</span>
</div>
</li>
}
{
hourTempNumber > hourNow &&
<li>
<div className="titulo" key={weatherA.temp_c}>
<span>{hourTempNumber}</span>
</div>
<div className="temperatura">
<img src={weatherA.condition.icon} alt={weatherA.condition.text} />
<span>{weatherA.temp_c}º</span>
</div>
</li>
}
</>
)
})
}
</ul>
</div>
</div>
</Styled.Container>
</>
)
}
Weather.tsx
import { CityWeatherSearch } from "./WeatherC/CityWeatherSearch";
import { MainWeatherLive } from "./WeatherC/MainWeatherLive";
import { WeatherDetails } from "./WeatherC/WeatherDetails";
import coldImage from '../assets/cold.jpg'
import sunImage from '../assets/sun.jpg'
import rainImage from '../assets/rain.jpg'
import nightVideo from '../assets/night.mp4'
import night from '../assets/night.jpg'
import { useState } from "react";
export const TypesWeather = {
NIGHT:{
video:{
source: nightVideo
},
image:{
source: night
}
},
OVERCAST:{
video:{
source: nightVideo
},
image:{
source: night
}
},
COLD:{
image:{
source: coldImage,
title: 'Frio'
}
},
SUN:{
image:{
source: sunImage,
title: 'Verão'
}
},
RAIN:{
image:{
source: rainImage,
title: 'Chuva'
}
},
};
export type TypesWeatherV2 = keyof typeof TypesWeather;
export function Weather(){
const [city,setCity] = useState('');
return (
<>
<div className="globalSite" style={{background:`linear-gradient(to bottom,rgba(0,0,0,.85) 0,rgba(0,0,0,.85) 100%),url(${TypesWeather.RAIN.image.source})`}}>
</div>
<div className="boxAllWeather">
<div className="backgroundWeather" style={{backgroundImage:`url(${TypesWeather.RAIN.image.source})`}}></div>
<div className="boxAllInff">
<div className="mainWeather">
<MainWeatherLive city={city} />
</div>
<div className="otherInfoWeather">
<CityWeatherSearch city={city} setCity={setCity}/>
<WeatherDetails city={city} setCity={setCity} />
</div>
</div>
</div>
</>
)
}
When I search for a city or state and click search, the name appears normally, but without the updated information
When I save the component responsible for this information, it is updated
I don't know what to do, can anyone give me an idea?

Pass information from an input to another component in another file

I'm having a problem, it's been a few days, I'm studying about React and Typescript and I'm developing a temperature application, I'm stopped in a part, where I want the user to click on the submit form, the information that was typed in the input is passed to another component.
Follow my two codes below
CityWeatherSearch.tsx
import { MagnifyingGlass } from 'phosphor-react'
import { FormEvent, useCallback, useRef, useState } from 'react';
import * as Styled from './style'
export function CityWeatherSearch(){
const inputRef = useRef<HTMLInputElement>(null);
const [city,setCity] = useState('');
function handleClick(event:FormEvent) {
event.preventDefault();
const inputCity = inputRef?.current?.value;
console.log({
inputCity, city
});
}
return(
<>
<Styled.BoxSearchCity>
<div className="headerSearch">
<form>
<input type="text" placeholder='Procurar Cidade...' ref={inputRef} onChange={
event => setCity(event.target.value)} />
<button type="submit" onClick={handleClick}>
<MagnifyingGlass/>
</button>
</form>
</div>
<div className="bodySearch">
{city}
</div>
</Styled.BoxSearchCity>
</>
)
}
MainWeatherLive.tsx
import {Clock} from 'phosphor-react'
import { useFetch } from '../../GetData/useFetch'
import * as Styled from './style'
type DataWeather = {
name: string,
condition:{
text:string,
icon:string
},
temp_c:number,
hour:[{
temp_c:number,
time:string,
condition:{
text:string,
icon:string
}
}]
}
export function MainWeatherLive(){
const {dataLocation: dataWeatherApi, isFetching, dataCurrent:dataCurrentApi, dataForecast:forecastApi}
= useFetch<DataWeather>('/v1/forecast.json?key=aff6fe0e7f5d4f3fa0611008221406&q=Guarulhos?days=1&aqi=no&alerts=no');
return(
<>
<Styled.HeaderBox>
<h6>Weather Now</h6>
</Styled.HeaderBox>
<Styled.Container>
{isFetching &&
<p>Carregando...</p>
}
<div className="mainInformation">
<div className="temperatura">
<span>{dataCurrentApi?.temp_c}º</span>
</div>
<div>
A cidade é {cityName}
</div>
<div className="boxCidade">
<div className="cidade">
<span>{dataWeatherApi?.name}</span>
</div>
<div className="tempoHoras">
<span>
{new Date().toLocaleTimeString('pt-BR',{hour12:false, hour:'numeric',minute:'numeric'})} - {new Date().toLocaleDateString()}
</span>
</div>
</div>
<div className="iconeTem">
<img src={dataCurrentApi?.condition.icon} alt={dataCurrentApi?.condition.text} />
</div>
</div>
<div className="footerBox">
<div className="headerFooter">
<Clock/>
<span>Horários</span>
</div>
<div className="listaHorarios">
<ul className="boxTT">
{
forecastApi?.hour?.map(weatherA =>{
const hourTemp = weatherA.time.split(" ")[1].replace(":00","");
const hourTempNumber:number = +hourTemp;
const hourNow = new Date().getHours();
return(
<>
{
hourTempNumber == hourNow &&
<li>
<div className="titulo" key={weatherA.temp_c}>
<span>{hourTempNumber}</span>
</div>
<div className="temperatura">
<img src={weatherA.condition.icon} alt={weatherA.condition.text} />
<span>{dataCurrentApi?.temp_c}º</span>
</div>
</li>
}
{
hourTempNumber > hourNow &&
<li>
<div className="titulo" key={weatherA.temp_c}>
<span>{hourTempNumber}</span>
</div>
<div className="temperatura">
<img src={weatherA.condition.icon} alt={weatherA.condition.text} />
<span>{weatherA.temp_c}º</span>
</div>
</li>
}
</>
)
})
}
</ul>
</div>
</div>
</Styled.Container>
</>
)
}
Weather.tsx
import { CityWeatherSearch } from "./WeatherC/CityWeatherSearch";
import { MainWeatherLive } from "./WeatherC/MainWeatherLive";
import { WeatherDetails } from "./WeatherC/WeatherDetails";
import coldImage from '../assets/cold.jpg'
import sunImage from '../assets/sun.jpg'
import rainImage from '../assets/rain.jpg'
import nightVideo from '../assets/night.mp4'
import night from '../assets/night.jpg'
export const TypesWeather = {
NIGHT:{
video:{
source: nightVideo
},
image:{
source: night
}
},
OVERCAST:{
video:{
source: nightVideo
},
image:{
source: night
}
},
COLD:{
image:{
source: coldImage,
title: 'Frio'
}
},
SUN:{
image:{
source: sunImage,
title: 'Verão'
}
},
RAIN:{
image:{
source: rainImage,
title: 'Chuva'
}
},
};
export type TypesWeatherV2 = keyof typeof TypesWeather;
export function Weather(){
return (
<>
<div className="globalSite" style={{background:`linear-gradient(to bottom,rgba(0,0,0,.85) 0,rgba(0,0,0,.85) 100%),url(${TypesWeather.RAIN.image.source})`}}>
</div>
<div className="boxAllWeather">
<div className="backgroundWeather" style={{backgroundImage:`url(${TypesWeather.RAIN.image.source})`}}></div>
<div className="boxAllInff">
<div className="mainWeather">
<MainWeatherLive />
</div>
<div className="otherInfoWeather">
<CityWeatherSearch />
<WeatherDetails />
</div>
</div>
</div>
</>
)
}
I want to pass the city typed in CityWeatherSearch.tsx to MainWeatherLive.tsx. Where is the space 'A cidade é {cityName}' reserved, I've tried everything, but I haven't been able to, could you help me?
You can do this in several ways:
parent -> child : use props
child -> parent : use callback/event emitter
no direct relationship : consider using state management tool like
redux
Just lift your state uo to the parent component and pass if to the cild components as props:
function WeatherPage() {
const [city,setCity] = useState('');
return (
<>
<CityWeatherSearch city={city} setCity={setCity}/>
//...
<MainWeatherLive city={city}/>
//...
</>
)
}
function CityWeatherSearch({city, setCity}) {
// your code here, only without const [city, setCity] useState()
}
function MainWeatherLive({city}) {
// your code here, now you can access city
}
If your two components don't have a direct common parent and you don't want to pass down city and setCity through a deep component hierarchy, think about using useContext to share state within your application.

How do i create a conditional loading message in react

I have a react code here
i want to load data from an API but it's taking time to get the data, thus my function is failing
How do i set it that it should wait for the data before rendering
import Head from 'next/head'
import Link from 'next/link'
import Navbar from './Navbar'
import Template from './Template'
import { useState, useEffect } from 'react'
export async function getStaticProps() {
const response = await fetch('https://peegin.com/api/public/peegins/recent')
const data = await response.json()
return {
props: { data }
}
}
const Home = ({ data }) => {
return (
<div className="content">
<Head>
<title>Peegin Recent</title>
</Head>
<Navbar />
{title}
{data.map(peegin => (
<div className="preview" key={peegin.permalink}>
<h3 className="title">
{peegin.title}
</h3>
<p>{peegin.meaning}</p>
<p className="example">Example</p>
<p className="example-content">{peegin.example}</p>
<p className="origin">Origin: {peegin.origin}</p>
<div className="name">
<h4>By</h4> <h4 className="namegreen">{peegin.user.name}</h4> <h4>{peegin.created_at}</h4>
</div>
<p className="views">{peegin.views.view} Views</p>
</div>
))}
</div>
);
}
export default Home;
const Home = ({ data }) => {
// add this as fallback
if(!data){
return <h1>Loading..<h1/>
}
return (
<div className="content">
<Head>
<title>Peegin Recent</title>
</Head>
<Navbar />
{title}
// add this to check for data first then do map
{data && data.map(peegin => (
<div className="preview" key={peegin.permalink}>
<h3 className="title">
{peegin.title}
</h3>
<p>{peegin.meaning}</p>
<p className="example">Example</p>
<p className="example-content">{peegin.example}</p>
<p className="origin">Origin: {peegin.origin}</p>
<div className="name">
<h4>By</h4> <h4 className="namegreen">{peegin.user.name}</h4> <h4>{peegin.created_at}</h4>
</div>
<p className="views">{peegin.views.view} Views</p>
</div>
))}
</div>
);
}

Resources