Cannot set data in variable after loading with useEffect() - reactjs

So I am doing an online fullstack course for University of Helsinki and it is wanting me to use a weather and climate API to show information about the country and its current weather.
So I have three components: App, Lister, and Country. Inside App I make a useEffect call with axios to get the data for a list of countries. From there I want it so that when you click on the button or search it should show information about the country, including weather. To do that, when information about the specific country is shown, I have another useEffect call to an API for the weather. However, while the first API call seems to store the data on the list of countries, the second call does not.
I have tried changing the dependency of the second useEffect call to [name], but that also does not bring results, and when I just log the information with that type of dependency, I just see an infinite amount of calls in the console. Have tried with other API's just to see if I was miscalling it somehow but even those resulted in the same situation.
Inside the App component I do a call to get info about all the countries and it works fine.
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import Lister from './Lister';
const App = () => {
const [places, setPlaces] = useState([]);
const [search, setSearch] = useState('');
const [results, setResults] = useState('');
useEffect(() => {
axios.get('https://restcountries.eu/rest/v2/all').then(r => setPlaces(r.data));
}, []);
.....
Everything works fine in here. I'm able to see and search and all that. I then have it call Lister which just maps the data from the country-search API into using the Country component.
.....
const listPlaces = () => {
if (results.length === 1) {
return (
<div>
{results.map(r => (
<Country
key={r.population / r.name.length + 5}
name={r.name}
capital={r.capital}
population={r.population}
languages={r.languages}
image={r.flag}
/>
))}
</div>
);
}
This is where the main crux of the issue is, in Country. When I call useeffect again, this time for the weather api, it doesn't seem to set it to the weather variable
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const Country = ({ name, capital, population, languages, image }) => {
const [weather, setWeather] = useState([]);
// have tried useState(), useState([]), useState({})
const accessKey = '';
useEffect(() => {
axios
.get(`http://api.weatherstack.com/...`)
.then(r => {
setWeather(r.data);
console.log(r.data);
});
}, []);
return (
<div>
<div className='Country'>
<h1>Name: {name}</h1>
<p>Capital: {capital}</p>
<p>Population: {population}</p>
<h2>Languages</h2>
<ul>
{languages.map(l => (
<li key={l.name.length}>{l.name}</li>
))}
</ul>
<img src={image} alt="Country's flag" height='100' width='100' />
</div>
<div className='Weather'>
<h1>Weather {weather.current.temperature} </h1>
<h2>Temperature </h2>
</div>
</div>
);
};
export default Country;
Weather will be of type undefined even though the log shows that the data was loaded, so should it not be set by the setWeather call?

Related

Why is my React Use-Effect Hook Not Working?

I am re-posting a question I asked a week ago where I left out the steps I took to solve the issue. I am working on a simple MERN app off a tutorial and the use-effect function is not rendering the content onto the page. Here is the code:
App.js File
import './App.css';
import { useState, useEffect } from 'react';
import Axios from 'axios';
function App() {
const [listOfUsers, setListOfUsers] = useState([]);
useEffect(() => {
Axios.get("http://localhost:3001/getUsersFakeDataGen").then((response) => {
setListOfUsers(response.data)
})
}, [])
return (
<div className="App">
<div className="usersDisplay">
{listOfUsers.map((user) => {
return (
<div>
<h1>Name: {user.name}</h1>
<h1>Age: {user.age}</h1>
<h1>Username: {user.username}</h1>
</div>
)
})}
</div>
</div>
)
};
export default App;
I tested the functionality by commenting out the "useEffect()" function and putting in an object in the "useState([])" element of "function App()". That object did correctly render on the page, but when I deleted that object and un-commented useEffect(), the page was blank again.
I confirmed that my APIs are working because my API client (Thunder Client) is showing that the GET and POST requests are reading and writing to the database (MongoDB). Also, the server is working properly (confirmed by a console log).
Any suggestions would be appreciated. If more information is needed, please let me know. Thank you.
if your problem is not resolved, yet I suggest the following:
import axios from 'axios'
...
const [listOfUsers, setListOfUsers] = useState([]);
const fetchData = async () => {
const result = await axios.get("http://localhost:3001/getUsersFakeDataGen").then((response) => {
setListOfUsers(response.data)
return response.data;
});
useEffect(() => {
fetchData();
}, [])
Note [] in the useEffect, it means it will render only once when the page loads. Also I used async and await to wait for data to be retrieved before processing (maybe that's why you get empty elements). You can also setState outside of useEffect.
import './App.css';
import { useState, useEffect } from 'react';
import Axios from 'axios';
function App() {
const [listOfUsers, setListOfUsers] = useState([]);
useEffect(() => {
Axios.get("http://localhost:3001/getUsers").then((response) => {
setListOfUsers(response.data)
})
}, [listOfUsers]); // THIS IS WHERE YOU ADD YOUR useEffect DEPENDENCIES
return (
<div className="App">
<div className="usersDisplay">
{listOfUsers.map((user) => {
return (
<div>
<h1>Name: {user.name}</h1>
<h1>Age: {user.age}</h1>
<h1>Username: {user.username}</h1>
</div>
)
})}
</div>
</div>
)
};
export default App;
OK look so the issue is if you only provided an empty array as your second argument. Your useEffect will only run one time, when you add stateful values to the array the useEffect will render every time that piece of state changes. If you omit the second argument the useeffect will run over and over again.
Also here-- Remember that you array starts empty, You need a check it's existence
{listOfUsers?.map.map((item))=>{}}
or
{listOfUsers.length && listOfUsers.map((item))=>{}}
.
{listOfUsers.map((user) => {
return (
<div>
<h1>Name: {user.name}</h1>
<h1>Age: {user.age}</h1>
<h1>Username: {user.username}</h1>
</div>
)
})}

How to populate my const before the page renders?

I'm pretty new to ReactJS dev and today i'm trying to get some data from an API using Axios. My issue is :
I'm trying to de map function on resultData to get what i want inside, but an error is proped and it's showing : resultData.map is not a function
If i comment this part of the code and just render the page first, then uncomment, it works and data are shown.
I'm assuming that data is not loaded before the rendering process is over so that's why i get this. But how to make it load before ?
Here my code snippets :
import React, { useState, useEffect } from "react";
import "./App.css";
import axios from "axios";
const Url = "someAPi";
function App() {
const [baseData, setBaseData] = useState({});
const [resultData, setResultData] = useState({});
useEffect(() => {
getBaseDataWithAxios();
}, []);
const getBaseDataWithAxios = async () => {
const response = await axios.get(Url);
setBaseData(response.data);
};
useEffect(() => {
getResultDataWithAxios();
}, []);
const getResultDataWithAxios = async () => {
const response = await axios.get(Url);
setResultData(response.data.result);
};
const listItems =
resultData.map((d) => <li key={d.value}>{d.value}</li>);
return (
<div className="App">
<header className="App-header">
<h2>generated fees</h2>
</header>
<div className="info-container">
<h5 className="info-item">{baseData.status}</h5>
<h5 className="info-item">{baseData.message}</h5>
<h5 className="info-item">{listItems[1]}</h5>
</div>
</div>
);
}
export default App;
The error is thrown on this :
const listItems =
resultData.map((d) => <li key={d.value}>{d.value}</li>);
I know my data can be read since if i comment the listItems and the displaying part in the return, render the page, uncomment everything, it displays the data properly.
Can someone explain to me how to populate data first ? During my research i've seen that this can happen by using Axios.
Thanks a lot !
The useEffect hook always runs after your component function returns in the render cycle.
Try an empty array for your initial value of resultData instead of an empty object:
const [resultData, setResultData] = useState([]);
There is no map built-in method on non-array objects, so during the first execution, you receive that error.

How to prevent loading data from API before user input in react?

I want to display data from an API, which user input will be part of the API URL. To be more precise, the goal is to let user enter an ETH wallet address, and display the NFT assets this wallet has, using Opensea API.
My problem is that the data is fetched and displayed before user submit input. (It's also a valid api url but not the data I want to fetch).
How to fix this issue? I think one way is to keep a boolean state of submitted, and display only if it is true. But this way it makes the api call regardless, although not rendered. Is there a better way? Does it matter what I set as the initial state for owner?
My guess is that there needs an async function, and the api fetch is callback. Trigger is the user input event. I'm not sure how to construct this.
Below is my code.
import { useState, useEffect } from "react";
// example user input: 0x147412d494731cbb91dbb5d7019464a536de04dc
function App() {
const [data, setData] = useState({ assets: [] });
const [enteredWallet, setEnteredWallet] = useState("");
const [owner, setOwner] = useState("");
const walletChangeHandler = (event) => {
setEnteredWallet(event.target.value);
};
const submittedHandler = (event) => {
event.preventDefault();
setOwner(enteredWallet);
console.log(enteredWallet);
};
useEffect(() => {
fetch(
`https://api.opensea.io/api/v1/assets?owner=${owner}&order_direction=desc&offset=0&limit=10`
)
.then((res) => {
return res.json();
})
.then((data) => {
setData(data);
});
}, []);
return (
<div className="App">
<header className="App-header">
<h3>Show me assets in this wallet</h3>
<form onSubmit={submittedHandler}>
<input
placeholder="wallet address"
value={enteredWallet}
onChange={walletChangeHandler}
/>
<button>Submit</button>
</form>
<div>
{data.assets.map((i, index, k) => (
<li key={index}>{i.name}</li>
))}
</div>
</header>
</div>
);
}
export default App;
p.s. I know this fetch api call is not the best approach. it's just for my exercise.
On a separate note, i also got a warning message "react Hook useEffect has a missing dependency: 'owner'. Either include it or remove the dependency array react-hooks/exhaustive-deps" Any clue?
It could look something like
const [data, setData] = useState()
const [owner, setOwner] = useState()
useEffect(() => {
if(owner){
fetch(...).then(setData)
}
}, [owner])
return data ? <>something with data goes here</> : <>Loading...</>

Why It Renders 'Undefined' first And Then Display The Object In The Log [duplicate]

This question already has an answer here:
Why does useState cause the component to render twice on each update?
(1 answer)
Closed 2 years ago.
How should I fix my React App which renders two logs with different results even though the code has only one console.log in the application? Yes, I removed <React.StrictMode> in my index.js file because it also trigger renders twice. In the terminal the first log is an undefined and the second one has the object with data, because of that when I use array map method, it keeps saying " BookDetail.jsx:26 Uncaught TypeError: bookData.map is not a function" .
I'm trying to fetch the detailed information about a book from the firebase database. My goal is very simple, a frontend user clicks a title of book and it takes to the detailed page. All the data stores in firestore database. The detailed page code is below, hoping somebody can help me out, thank you!
import React, {useState, useEffect} from 'react'
import {Link} from 'react-router-dom'
import firebase from '../config/fbConfig'
const BookDetails = (props) => {
const id = props.match.params.id
const db = firebase.firestore()
const [books, setBooks] = useState('')
useEffect(() => {
db.collection("books")
.doc(id)
.get()
.then(doc => doc.data())
.then(data => setBooks(data))
},[])
const bookData = {books}
return (
<div className="book_details">
<Link to="/"><h2>Home</h2></Link>
{console.log(bookData)}
<h1>The Summary Of the Book </h1>
{bookData.map(book => <div key={book.id}> {book.brief} </div>)}
</div>
)
}
export default BookDetails
Per ReactJS documentation, it's recommended to call hooks at top level of component: https://reactjs.org/docs/hooks-rules.html
So it looks like when your component mounts it calls your custom useBooks hook.
It initializes books state and then executes the useEffect. However, I think calling to the Firestore db function is an async process and because you're not waiting for the response, your function is returning undefined.
It seems like you may not need your custom hook. Set up useState and useEffect at the top level.
const BookDetails = (props) => {
const id = props.match.params.id
const db = firebase.firestore()
const [books, setBooks] = useState([])
useEffect(() => {
const fetchBooks = () => {
db.collection("books")
.doc(id)
.get()
.then(doc => doc.data())
.then(data => setBooks(data))
}
fetchBooks()
},[])
return (
<div className="book_details">
<Link to="/"><h2>Home</h2></Link>
{console.log(books.title)}
<h1>The Summary Of the Book </h1>
{books && books.map(book => <div key={book.id}> {book.brief} </div>)}
</div>
)
}
export default BookDetails
const data = doc.data() appears to be an async function, so you may want to chain another .then for setBooks

Display loading indicator until all the images in a gallery are (fully) loaded from an API in React (using Hooks)

I'm learning React and I'm trying to make a simple site which is basically a Giphy search engine by using their API.
So far so good I am fetching and displaying data (the trending Giphy images). The problem is that I don't like how my loading indicator works. It shows for a bit but when it disappears, the (40) images are still being populated in the container.
Is there a way that I can make the loading indicator disappear only when everything is loaded?
<Loader> is my loading indicator. I'm also using some Reactstrap components.
Here are my current 2 components:
App.js
import React, { useState, useEffect } from 'react'
import "react-loader-spinner/dist/loader/css/react-spinner-loader.css"
import Loader from 'react-loader-spinner'
import Results from './Components/Results'
import { UncontrolledAlert } from 'reactstrap'
function App() {
const [isLoading, setLoading] = useState(true)
const [gifsData, setGifsData] = useState([])
const [errorMessage, setErrorMessage] = useState('')
useEffect(() => {
const giphyTrending = async () => {
await fetch(`https://api.giphy.com/v1/gifs/trending?api_key=OGINPHAsY1NNNhf6XIlpX1OygKXDFfXV&limit=50&rating=R`)
.then(res => res.json())
.then(data => {
setGifsData(data.data)
setLoading(false)
})
.catch(err => setErrorMessage(err.message))
}
giphyTrending()
}, [])
if (errorMessage) {
return (
<div>
<UncontrolledAlert color="secondary">{errorMessage}</UncontrolledAlert>
</div>
)
}
return (
<div className='App'>
{isLoading ?
<Loader className='loader' type="Circles" color="yellow" height={120} width={120} />
:
<Results isLoading={isLoading} gifsData={gifsData} />}
</div>
)
}
export default App
Results.jsx (not sure this one is needed but just in case)
const Results = (props) => {
return (
<div className='gifsContainer'>
{props.gifsData.map(gif => (
<div key={gif.id}>
<CardImg className='gifs' src={gif.images.fixed_height_small.url} alt={gif.title} />
</div>))}
</div>
)
}
export default Results
Looking at this SO question it looks like you can provide an onLoad prop to an img (I'm guessing that's what you're using under the hood of CardImg). If so, you can have each of those fire off a function after they're loaded, keep track of those in your parent component, and when the count of the images you received from your fetch match the count of the images loaded, then you can remove the loading indicator.
This is making a few assumptions. Let me know if you'd like me to sketch out that flow for you.
Check this link you could maintain a count in state and guarentee all images are loaded by hitting 0. So loading = loading.state and imageload.state.count != 0.
https://www.javascriptstuff.com/detect-image-load/
For example, we define a counter:
const [counter, setCounter] = useState(0);
At CardImage component, we use OnLoad function props to update counter when image is loaded completely. And while counter is not equal to gifsData.length - 1 the IndicatorView will be displayed

Resources