multiple useEffect in a component doesn't work - reactjs

When I call API from single useEffect, it works perfectly. But when I am trying to call another API from another useEffect in the same component its shows a error.
If it's possible, please have a look at my project on codesandbox.
import React, { useEffect, useState } from 'react';
import { Container, Row, Col } from 'react-bootstrap';
const TeacherDashboard = () => {
// console.log(props)
const [appointmentList, setAppointmentList] = useState([]);
const [viewProfile, setViewProfile] = useState([]);
console.log(viewProfile);
useEffect(() => {
async function loadData(){
const response = await fetch('http://localhost:4200/appointments')
const data = await response.json();
setAppointmentList(data)
}
loadData()
}, [appointmentList])
useEffect(() => {
async function proData() {
const response = await fetch('http://localhost:4200/schedule')
const data = await response.json();
setViewProfile(data)
}
proData()
}, [viewProfile])
return (
<Container>
<Row>
<Col>
{
appointmentList.map(app =>
<div style={{border: '1px solid blue'}}>
<li>Name : {app.name} </li>
<li>Id : {app.s_id} </li>
<li>Sec : {app.sec} </li>
<li>Email : {app.email} </li>
<li>Date & Time : {app.dateTime} </li>
</div>
)
}
</Col>
</Row>
</Container>
);
};
export default TeacherDashboard;

I am not sure the purpose of setting both appointmentList and viewProfile states as the part of the dependency arrays of both useEffect hooks. Both of them will eventually result in an infinite loop as you are directly updating the respective states in the useEffect hooks.
From what I can see, you only need to make both requests once, thus you should be using an empty array as the dependency array, such that both requests will be called only when the component is mounted. This is how it can be done:
useEffect(() => {
async function proData() {
const response = await fetch('http://localhost:4200/schedule')
const data = await response.json();
setViewProfile(data)
}
proData();
async function loadData(){
const response = await fetch('http://localhost:4200/appointments')
const data = await response.json();
setAppointmentList(data)
}
loadData();
}, []);

Related

useState variable is called before useEffect API call

From what I understand useEffect hook runs last as a sideEffect. I am attempting to console log data.main.temp. I can understand that it doesn't know what that is yet, because it is fetching the data from the api in the useEffect hook which runs after.
How would I be able to access or console log data.main.temp AFTER the api call? (I feel like setTimout is the cheating way?)
import React, { useState, useEffect } from "react";
import Button from "../UI/Button";
import styles from "./Weather.module.css";
import moment from "moment";
import Card from "../UI/Card";
export default function Weather() {
//State Management//
const [lat, setLat] = useState([]);
const [long, setLong] = useState([]);
const [data, setData] = useState([]);
//openWeather API key
const key = "xxxxxxxxxxxxxxxxxxxxxxxxxx";
useEffect(() => {
const fetchData = async () => {
//get coordinates//
navigator.geolocation.getCurrentPosition(function (position) {
setLat(position.coords.latitude);
setLong(position.coords.longitude);
});
//fetch openWeather api//
await fetch(`https://api.openweathermap.org/data/2.5/weather/?lat=${lat}&lon=${long}&units=metric&APPID=${key}`)
.then((res) => res.json())
.then((result) => {
setData(result);
console.log(result);
});
};
fetchData();
}, [lat, long]);
//Examples of what I want, they run too early before api//
console.log(data.main.temp);
const Farenheit = data.main.temp * 1.8 + 32;
return (
<Card>
{typeof data.main != "undefined" ? (
<div className={`${styles.weatherContainer} ${styles.clouds}`}>
<h2>Weather</h2>
<p>{data.name}</p>
<p>{data.main.temp * 1.8 + 32} °F</p>
<p>{data.weather[0].description}</p>
<hr></hr>
<h2>Date</h2>
<p>{moment().format("dddd")}</p>
<p>{moment().format("LL")}</p>
</div>
) : (
<div></div>
)}
</Card>
);
}
You're right, the effect function is run after the first render which means you need to wait somehow until your api call is done. One common way to do so is to introduce another state flag which indicate whether the data is available or not.
Another thing which does not follow react good practices is the fact that you're effect function does more than one thing.
I also added trivial error handling and cleaned up mixed promises and async await
here is your refactored code
import React, { useState, useEffect } from "react";
import Button from "../UI/Button";
import styles from "./Weather.module.css";
import moment from "moment";
import Card from "../UI/Card";
//openWeather API key
const key = "xxxxxxxxxxxxxxxxxxxxxxxxxx";
export default function Weather() {
//State Management//
const [lat, setLat] = useState();
const [long, setLong] = useState();
const [data, setData] = useState();
const [error, setError] = useState();
const [loading, setLoading] = useState(false);
useEffect(() => {
navigator.geolocation.getCurrentPosition((position) => {
setLat(position.coords.latitude);
setLong(position.coords.longitude);
});
}, []);
useEffect(() => {
const fetchData = async () => {
if (lat && long && key) {
try {
setLoading(true);
const response = await fetch(
`https://api.openweathermap.org/data/2.5/weather/?lat=${lat}&lon=${long}&units=metric&APPID=${key}`
);
const data = await response.json();
setData(data);
setLoading(false);
} catch (err) {
setError(err);
setLoading(false);
}
}
};
fetchData();
}, [lat, long]);
if (error) {
return <div>some error occurred...</div>;
}
return (
<Card>
{loading || !data ? (
<div>loading...</div>
) : (
<div className={`${styles.weatherContainer} ${styles.clouds}`}>
<h2>Weather</h2>
<p>{data.name}</p>
<p>{data.main.temp * 1.8 + 32} °F</p>
<p>{data.weather[0].description}</p>
<hr></hr>
<h2>Date</h2>
<p>{moment().format("dddd")}</p>
<p>{moment().format("LL")}</p>
</div>
)}
</Card>
);
}
You can use another useEffect, which depends on changing the data state
useEfect(() => {
if (data) {
// do something with data
}
}, [data])
You can create a simple function and call it in your API call response and pass in the data directly from the api response, that way you will have access to the data immediately there's a response.
E.g
...
.then((result) => {
setData(result);
getDataValue(result) // this function will be called when the response comes in and you can use the value for anything
console.log(result);
});
METHOD 2:
You can use a useEffect hook to monitor changes in the data state, so that whenever there's an update on that state, you can use the value to do whatever you want. This is my less preferred option.
useEffect(() => {
//this hook will run whenever data changes, the initial value of data will however be what the initial value of the state is
console.log(data) //initial value = [] , next value => response from API
},[data])

React Typescript fetch: useState UseEffect pokeapi

I am trying to learn how to fetch data and display in a table/list/graph in React.
I extracted the fetch to a component and while i can get the list to appear i think this is wrong - Why and how to fix?
// getData.tsx
import React, { useState, useEffect } from 'react';
let myArray: string[] = [];
export default function GetData() {
const [info, setData] = useState([]);
useEffect(() => {
getMyData();
}, []);
const getMyData = async () => {
const response = await fetch('https://pokeapi.co/api/v2/type')
const data =await response.json();
//console.log(data.results)
for (var i = 0; i < data.results.length; i++) {
myArray.push(data.results[i].name)
setData(data.results[i].name)
}
console.log(info)
}
return (
<div>
<h1>get Data</h1>
{myArray.map((value,index) => {
return <li key={index}>{value}</li>;
})}
</div>
)
}
Also same issue but do not understand why the names and Array don't both work?
export default function GetData(){
const names: string[] = ["whale", "squid", "turtle", "coral", "starfish"];
const theArray: string[] = [];
const getData = async () => {
const response = await fetch('https://pokeapi.co/api/v2/type');
const data = await response.json()
//for (var i = 0; i < data.results.length; i++) {
for (var i = 0; i < 5; i++) {
theArray.push(data.results[i].name)
}
console.log(theArray)
}
console.log(names)
console.log(theArray)
getData()
return (
<div>
<ul>{names.map(name => <li key={name}> {name} </li>)}</ul>
<h1>get Data</h1>
<ul>{theArray.map(name => <li key={name}> {name} </li>)}</ul>
</div>
)
}
You aren't using the state data... The issue is that.
The correct way to do this:
const [data, setData] = useState([])
useEffect(() => {
fetch('https://pokeapi.co/api/v2/type')
.then(res => res.json())
.then(setData)
},[])
return <div>
<ul>
{data.map((name) => <li key={name}>{name}</li>}
</ul>
</div>
The problem is getData is declared as async function. That means it's returning Promise that you can await on and get it's result. But you never do that. You're using it without await essentially not waiting for it finish and discarding its result.
To get the result of async function you should await on it. In your second component you'll have to write this:
...
console.log(names)
console.log(theArray)
await getData() // add 'await' to well... wait for the result of the getData execution
return (
...
But you can await only inside async function aswell. As far as I'm concerned you're not able to use async components now (react#16-17). So the second component is not going to work as intended. At least untill react is able to support async components.
Though there are some issues even with your first component.
let myArray: string[] = [];
Declared in the module scope it will be shared (and not reseted) between all instances of your component. That may (and will) lead to very unexpected results.
Also it's quite unusuall you don't get linting errors using getMyData before declaring it. But I suppose that's just an artefact of copy-pasting code to SO.
Another problem is you're using setData inside your component no to set the contents of myArray but to trigger rerender. That's quite brittle behavior. You should directly set new state and react will trigger next render and will use that updated state.
To work properly your first component should be written as:
import React, { useState, useEffect } from 'react'
export default function GetData() {
const [myArray, setMyArray] = useState([])
const getMyData = async () => {
const response = await fetch('https://pokeapi.co/api/v2/type')
const data = await response.json()
const names = data.results.map((r) => r.name) // extracting 'name' prop into array of names
setMyArray(names)
}
useEffect(() => {
getMyData();
}, []);
return (
<div>
<h1>get Data</h1>
{myArray.map((value,index) => (
<li key={`${index}-${value}`}>{value}</li>
))}
</div>
)
}

Cannot read property 'map' of undefined on useState value

I'm new to react, I'm getting this error constantly and after google some I can't find the reason why the useState value can't be read as array :( ... this the error I'm getting: 'TypeError: team.map is not a function'
import React, { useEffect, useState } from "react";
const SportTeams = () => {
const [team, setTeam] = useState([]);
useEffect(() => {
const getSports = async () => {
const response = await fetch("https://www.thesportsdb.com/api/v1/json/1/all_sports.php");
const data = await response.json();
setTeam(data);
console.log(data);
}
getSports();
}, []);
return (
<div className="myClass">
<ul>
{team.map((sport, index) => {
return <li key={`${sport.strSport}-${index}`}>{sport.strSport}</li>
})}
</ul>
</div>
);
};
export default SportTeams;
Just update setTeam like following, your error will be resolved.
setTeam(data.sports);
It is because you are setting the team state with the data without checking if its undefined. If the data is undefined your state team become undefined as well. So make sure to check the data.
import React, { useEffect, useState } from "react";
const SportTeams = () => {
const [team, setTeam] = useState([]);
useEffect(() => {
const getSports = async () => {
const response = await fetch("https://www.thesportsdb.com/api/v1/json/1/all_sports.php");
if (response) {
const data = await response.json();
if (data) {
setTeam(data);
}
}
console.log(data);
}
getSports();
}, []);
return (
<div className="myClass">
<ul>
{team.map((sport, index) => {
return <li key={`${sport.strSport}-${index}`}>{sport.strSport}</li>
})}
</ul>
</div>
);
};
export default SportTeams;
There might also be the chance that your response is not what you expected and the actual data might be inside your response. In that case you need check what your response first then proceed to set the data.
As I said in my comment. the value you are setting to teams isn't an array.
const data = await response.json();
setTeam(data.sports);

refetch data from API using react hooks

I'm a complete beginner in react and I have written a fetch component which returns data from an API using a usefetch function . In my app I can manually change the input to get different data from the API but what I want is to have an input field and a button that when it is clicked it returns the new data from the API . With my code below I can fetch data only once when the component mounts and if i give input nothing happens .
import React , {useState ,useEffect} from 'react';
import useFetch from './fetch'; //fetch api code imported
import SearchIcon from '#material-ui/icons/Search';
import InputBase from '#material-ui/core/InputBase';
import Button from '#material-ui/core/Button';
function City(){
const searchStyle = {
display:"flex",
justifyContent:"flex-start",
position:"absolute",
top:"400px",
left:"40%",
}
const [inputVal , setInputVal] = useState(''); //store input value
const [place,setPlace] = useState('london'); //get london data from api by manually changing value new data is succesfully dislayed
const {loading , pics} = useFetch(place); //fetch data
const [images , setImages] = useState([]); //store fetched imgs
const removeImage = (id) =>{
setImages((oldState)=>oldState.filter((item)=> item.id !== id))
}
useEffect(()=>{
setImages(pics);
} , [pics] )
//load and display fetched images
return (<div className="city-info">
{
!loading ?
(images.length>0 && images.map((pic) =>{
return <div className="info" key = {pic.id}>
<span className="close" onClick= {()=>removeImage(pic.id)} >
<span
className="inner-x">
×
</span>
</span>
<img src = {pic.src.original} alt ="img"/>
<div style = {{position:"absolute" ,margin:"10px"}}>
<strong>From : </strong>
{pic.photographer}
</div>
</div>
})
):<div> Loading </div>
}
<div style = {searchStyle} >
<SearchIcon />
//when input changes store it
<InputBase onChange={(e)=>setInputVal(e.target.value)} placeholder="Enter input" style= {{backgroundColor:"lightgrey"}}/>
//new fetch data based on input by clicking on button nothing happens onclick
<Button onClick= {()=>setPlace(inputVal)} color="primary" variant = "contained" > Find </Button>
</div>
</div>);
}
export default City;
fetch.js my code to connect to api :
import { useState, useEffect } from 'react';
function useFetch(url){
const [loading ,setLoading] = useState(false);
const [query,setQuery] = useState(url);
const [pics,setPics] = useState([]);
const getPics = async()=>{
setLoading(true);
const response = await fetch(
`https://api.pexels.com/v1/search?query=${query}&per_page=4`,
{
method:"GET",
headers:{
Accept:"application/json",
Authorization:key
}
}
);
const result = await response.json();
setPics(result.photos ?? []);
setLoading(false);
}
useEffect(()=>{
getPics();
},[query]);
return {loading , pics ,query ,setQuery , getPics};
}
export default useFetch;
I think that my place value changes when my button is clicked but my fetch function is not reloaded and I just change a value .
I would really appreciate your help .
You can create a new useEffect and then add the place to the useEffect dependencies to create a side effect to call the API again once the value of the place variable changes:
// return the read function as well so you can re-fech the data whenever you need it
const {loading , pics, readData} = useFetch(place);
useEffect(() => {
readData(place);
setImages(pics)
}, [place]);
This will give you fresh data for each button click.
The problem is useFetch is storing the initial url passed into useState:
const [query,setQuery] = useState(url);
When place gets updated, useFetch never uses it and the effect is never going to be re-triggered. I think if you remove this state from useFetch completely, it ought to work as you expect:
import { useState, useEffect } from 'react';
function useFetch(url) {
const [loading, setLoading] = useState(false);
const [pics, setPics] = useState([]);
const getPics = async () => {
setLoading(true);
const response = await fetch(
`https://api.pexels.com/v1/search?query=${query}&per_page=4`,
{
method: "GET",
headers: {
Accept: "application/json",
Authorization: key
}
}
);
const result = await response.json();
setPics(result.photos ?? []);
setLoading(false);
}
useEffect(()=>{
getPics();
}, [url]);
return { loading, pics, getPics };
}
export default useFetch;

Simple function that retrieves data from an API is not returning the data

I have this React component that used to return an HTML element like this:
const PartsList = () => {
const [data, setData] = useState([]);
useEffect(() => {
const fetchData = async () => {
const result = await axios(
'https://localhost:44376/api/parts',
);
setData(result.data);
};
fetchData();
}, []);
return (
<>
{data.map((item, index) => (
<label key={index} className="inline">
<Field key={index} type="checkbox" name="machineParts" value={item.id} />
{item.name}
</label>
))}
</>
);
}
export default PartsList;
Now, I want it to return only an array of JSON, no HTML.
So I tried modifying the component so that it looks like this:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const [data, setData] = useState([]);
useEffect(() => {
const fetchData = async () => {
const result = await axios(
'https://localhost:44376/api/machines',
);
setData(result.data);
console.log("data as seen in function: ", JSON.stringify(result, null, 2));
};
fetchData();
}, []);
return data;
When I write it out to the console in this function, I see all the needed data.
But when I write it out to the console in the main App.js, I just see undefined.
What could I be doing wrong?
Thanks!
Originally you wanted a component because it had to render HTML.
Now what you actually need is to move everything out to a function.
So you can do this in your main App.js:
import React from 'react';
import axios from 'axios';
const fetchData = async () => {
const result = await axios(
'https://localhost:44376/api/machines',
);
return JSON.stringify(result, null, 2);
};
const App = () => {
const result = await fetchData()
console.log(result)
return <div>Main App<div>
}
export default App
This is how you make a function to return data that you can call to see the console result in your main App component.
This obviously just demonstrates the concept, you can take it further by moving that function out to its own file that you can import into your App.js folder.

Resources