How to show loading only after making a api request? - reactjs

In my react Project,
There is a button. When clicking on it, it will start making an API request.
Right now, on the first page load, there is already a "Loading" showing, even before I click the right button.
Q1: How can I only show the "Loading" only after I set the click loading butting?
(For some reason, I am not able to use setLoading state to do this)
Q2:Even thought this example may seem so trivial, but taking the consideration that if the return is Error or resolved, there may be different handling, even thought I havent shown it in the example yet.
I have read some online doc, it said I may need to use useReducer for this. But I am not so sure how.
Notice: Much appreciate if could provide answer by using useReducer approach
Below is my code
import React , {useState, useEffect}from 'react';
import axios from 'axios';
export function App(props) {
const [post, setPost]= useState('')
useEffect(()=>{console.log(post)})
const handle = () => {
axios.get('https://jsonplaceholder.typicode.com/todos/1').then((response) => {
setPost(response.data);
});
}
return (
<div className='App'>
<button onClick={handle}>Load data</button>
{post? <ul>
<li>{post.userId}</li>
<li>{post.id}</li>
<li>{post.title}</li>
</ul>:<p>Loading</p>
}
</div>
);
}
=====================
Edited:
I have updated the code, but now I am stuck with the situation that when the loading is set to be true, the databoard is gone then
import React , {useState, useEffect}from 'react';
import axios from 'axios';
export function App(props) {
const [post, setPost]= useState('')
const [loading,setLoading]= useState(false);
useEffect(()=>{console.log(post)})
const handle = () => {
//before do the api call
//set the loading
setLoading(true);
axios.get('https://jsonplaceholder.typicode.com/todos/1').then((response) => {
setPost(response.data);
//set the loading to be false as loading is done
setLoading(false);
}).catch((err) => {
//error state
//set the loading to be false as loading is done
setLoading(false);
});
;
}
return (
<div className='App'>
<button onClick={handle}>Load data</button>
{loading? <Databoard post={post}>
</Databoard>:null
}
</div>
);
}
const Databoard = (props) => {
const {post}=props
return <ul>
<li>{post.userId}</li>
<li>{post.id}</li>
<li>{post.title}</li>
</ul>
}

You simply need a state variable using which you can judge what to show to the user.
const [loading,setLoading]= useState(false);
const handle = () => {
setLoading(true); axios.get('https://jsonplaceholder.typicode.com/todos/1').then((response) => {
setPost(response.data);
setLoading(false);
}).catch((err) => {
//error state
setLoading(false);
});
}
Use the variable in your return statement to handle the jsx

Related

Difficulties with useEffect and asyncawait

I've read several questions here regarding my current difficulty; they also told me the way I was coding it was wrong, and I changed it. However, even after changing I still can't seem to get the proper result.
I'm trying to make a small React HTTP Request app for learning purposes. According to the classes I've been following, I managed to create the json server, setup to watch for the DB.json properly, etc. Now, inside the App.js I'm trying to make the async\await call for listing the products in the console.
First, I had the following error:
"Effect callbacks are synchronous to prevent race conditions. Put the async function inside:"
I fixed it by changing my code. It was triggering a warning and I found out the classes I've been following are a bit outdate. No problem. However, even after changing it I can't view the products I create on db.json. If I go to localhost:3000/products it shows up there (which means things are working).
I believe I'm doing it the right way now, but I still can't seem to figure out why I can't view the data.
Any input is appreciated. Thanks!
ps: I'm just starting with react.
App.Js
import './App.css';
import { useState, useEffect } from "react";
const url="http:/localhost:3000/products";
function App() {
const [products, setProducts] = useState ([]);
useEffect(() => {
const fetchData = async () => {
const data = await fetch(url);
console.log("Data:" + data)
const res = await data.json();
console.log("Res:" + res)
setProducts(res);
}
fetchData();
}, []);
console.log(products);
return (
<div className="App">
<h1>LIsta de produtos</h1>
</div>
);
}
export default App;
The URL you put is missing a "/", Check if the URL is right, rest else seems to be correct, the code below should work.
import "./App.css";
import { useState, useEffect } from "react";
// URL is probably wrong, this is fixed URL
const url = "http://localhost:3000/products";
function App() {
const [products, setProducts] = useState([]);
useEffect(() => {
const fetchData = async () => {
const data = await fetch(url);
console.log("Data:" + data);
const res = await data.json();
console.log("Res:" + res);
setProducts(res);
};
fetchData();
}, []);
console.log(products);
return (
<div className="App">
<h1>LIsta de produtos</h1>
</div>
);
}
export default App;

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.

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

SWAPI request in React

I am trying to get SWAPI data from 'people' using react. I would ultimately like to retrieve the data and then set the people and create a card from the 10 people on page 1. When I console.log my response I am able to see the object returned. I am trying to set that using response.data.results (should contain people).
//full code:
import React, { useState, useEffect } from 'react';
import axios from "axios";
import Cards from "./components/Card"
function People() {
const [people, setPeople] = useState([]);
useEffect(() => {
axios.get('https://swapi.co/api/people/')
.then(res => {
//console.log(res);
setPeople(res.data.results)
})
.catch(err => {
console.log(`Data not received ${err}`)
})
}, [])
return (
<div className = "container">
{people.map((name, index) => {
return <Cards name={name} index={index}/>
})}
</div>
)
}
export default People;
When I console.log swPeople after using setswPeople I am returned an empty array.
Any ideas as to why the set is not giving me an array containing the 10 people on page one?
I see it working https://codesandbox.io/s/react-hooks-useeffect-frhmn
it take time to set the state , if we dont pass the second argument [] to useEffect you will see it is returning data correctly but that will cause the infinite loop , so we avoid that
import React, { useState, useEffect } from 'react';
import axios from "axios";
import Cards from "./components/Card"
function People() {
const [people, setPeople] = useState([]);
useEffect(() => {
axios.get('https://swapi.co/api/people/')
.then(res => {
//console.log(res);
setPeople(res.data.results)
})
.catch(err => {
console.log(`Data not received ${err}`)
})
}, [])
return (
<div className = "container">
{people.map((name, index) => {
return <Cards name={name} index={index}/>
})}
</div>
)
}
looks like this worked after all but it was taking close to 30s for me to see that info logged in console and I was being impatient
Have you tried to enter this url in your browser, https://swapi.co/api/people?
Because it seems the link is redirecting to another url while it needs to brign you back a JSON.
If you want to use SWAPI info replace you SWAPI to https://www.swapi.tech/api/people
it works well.
However I suggeust you to download the extension of JSONVue it will help you track your data with comfortable JSON view in your broweser.
And about the info of the 10 people you trying to get from SWAPI, when you'll open the browser with the new SWAPI adress, try to track the path you want to do in your code. You'll see the info you're trying to catch is leading to another API.

Resources