How to make a limited axios request? - reactjs

could someone lend me a hand at this?
I have a code that is working fine, it shows a list of all fire type Pokemon, the names and the images. It is showing all fire pokemon (100 pokemon more or less), but i want to request ONLY the first 10, the is a way to do this? (i have tried to put "params: _limit:10" at the axios.get function but didnt work.
export default class PokemonList extends Component {
state= {
url: 'https://pokeapi.co/api/v2/type/fire',
pokedata: null
};
async componentDidMount() {
const res = await axios.get(this.state.url);
this.setState({ pokedata: res.data['pokemon'] })
}
render() {
return (
<PanelArea>
{this.state.pokedata ? (
<div className='row'>
{this.state.pokedata.map(pokedata =>
<PokemonCard
key={pokedata.pokemon.name}
name={pokedata.pokemon.name}
url={pokedata.pokemon.url}
/>
)}
</div>
) : (
<h1> Carregando Pokemon </h1>
)}
</PanelArea>
)};
}

What you are trying to achieve is strictly an API level change.
Also pokemon API has a limit param that could be used to set the limit of items requested. I might suggest try to use that.
If for some reason that isn't working, you can have a workaround in your client-side code where you fetch the pokemon data.
Let's say that you are getting 100 items returned in an array. Before setting local state with this value, you could filter over the incoming results and make sure that you are just accepting the first 10 or last 10 or whatever your implementation maybe.
Update with further details:
The response which comes in, is an object, which has a key named, pokemon, this is an array. All you need to do is before setting the local state with pokemon data, have a wrapper function do certain manipulations.
function mapPokemonData = (data) => {
// let's assume you always need only ten items and that the API is
// always going to return you data more than 10
let updatedData = [];
if(data.length >= 10) {
updatedData = [...data].splice(0, 10)
} else {
updatedData = [...data]
}
return updatedData
}
Now you can take the returned value from this function and then set the local state value.

I have discovered a way that worked for me, i just added .slice(0,10) after the res.data request and worked out fine!
async componentDidMount() {
const res = await axios.get(this.state.url);
this.setState({ pokedata: res.data['pokemon'].slice(1,10) })
}

Related

How to access data from custom react hook

Preface: I'm fairly new to React (Coming over from Angular). I know things a similar but different.
I have referenced the following SO threads to no avail in my situation:
React not displaying data after successful fetch
Objects are not valid as a React child. If you meant to render a collection of children, use an array instead
Currently, I'm trying to get my data to display from an API I developed. I'm used to the Angular approach which would call for a ngFor in the template for most data showcase situations.
I'm having trouble wrapping my mind around what I have to do here in order to display my data. The data is expected to be an array of objects which I would then parse to display.
I also receive the following error: Error: Objects are not valid as a React child (found: object with keys {data}). If you meant to render a collection of children, use an array instead.
I've searched high and low for a solution but sadly, nothing I've seen has worked for me. (All of the answers on SO are using the class-based version of React, of which I am not).
You can see my data output in the following screenshot:
I am also including my custom hook code and the component that is supposed to render the data:
CUSTOM DATA FETCH HOOK
interface Drone{
id: number;
name: string;
model: string;
price: number;
}
export function useGetData(urlpath:string) {
const [droneData, setData] = useState<any>()
async function handleDataFetch(path:string){
const result = await fetch(`https://drone-collections-api-jc.herokuapp.com${path}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'x-access-token': 'Bearer API-TOKEN'
}
})
const response = await result.json();
setData(response)
}
useEffect( () => {
handleDataFetch(urlpath)
})
return droneData
}
THE DRONE COMPONENT
import { useGetData } from '../../custom-hooks'
export const Drones = () => {
let data = useGetData('/drones')
console.log(data)
// const DisplayDrone = ( ) => {
// return (
// Array.prototype.map( data => {
// <div>{ data.name }</div>
// })
// )
// }
return (
<div>
<h1>Hello Drones</h1>
</div>
)
}
Also, for more context, the current code can be found at this repo: https://github.com/carter3689/testing-drone-frontend
Please, help me understand what I'm missing. Many Thanks!
There are several locations that needed to be fixed
In fetchData.tsx
export function useGetData(urlpath: string) {
const [droneData, setData] = useState<any>([]);
async function handleDataFetch(path: string) {
const result = await fetch(`https://jsonplaceholder.typicode.com/posts`, {
...
});
const response = await result.json();
setData(response);
}
useEffect(() => {
handleDataFetch(urlpath);
}, []);
Explanation:
you need a "blank" array for looping through. I guess that the error causes by the fact that at the start, before the data is fetched, there is nothing to loop through. It's same as doing undefined.map(), which is obviously fail.
You need a dependencies array for useEffect. Right now your code will do an infinite loop since everytime it get data, it update the state, thus re-run the useEffect and repeat. Add dependencies array limit when that useEffect will run
In Drones.tsx
return (
<div>
{data.map(item => <div>{item.name}</div>}
</div>
)
Not much to say here. I don't use Angular so I'm not sure why you use Array.prototype.map, but in React you can loop through your variable directly. I also have a CodeSandbox link for your project (I use public API)

How to handle the case where the axios response comes after the component is rendered?

I am creating a blog application in rest framework and reactjs. On the home page, under componentDidMount, I send an API call using axios to get all the articles and setState of articles to the return. As I have studied, axios works on the idea of promise such that the code doesnt proceed, if the API is not fetched for a particular component. Please tell me, if I am wrong.
Then, I send a GET call to get the writer's name, who wrote the article by the id. Though, I assumed that the axios works as a promise. But, it doesnt work that way. Now, I am not sure how to move ahead.
Here is a snippet. So, in mainBody.js, I make the api call as:
class MainBody extends Component {
state = {};
componentDidMount () {
this.get_all_articles();
};
get_writer_name (id) {
let authstr = 'Bearer ' + window.localStorage.token;
let writer_url = "http://localhost:8000/api/writer/" + id.toString() + "/";
axios.get(writer_url, { headers: { Authorization: authstr }})
.then(response => {
console.log(response.data['name'])
return response.data['name'];
})
.catch(err => {
console.log("Got error")
})
};
get_all_articles () {
let authstr = 'Bearer ' + window.localStorage.token;
axios.get("http://localhost:8000/api/articles/", { headers: { Authorization: authstr }})
.then(response => {
this.setState({articles: response.data});
})
.catch(err => {
console.log("Got error")
})
}
render () {
return (
{this.state.articles.map((article, key) =>
<ArticleView key={article.id} article={article} writer_name={this.get_writer_name(article.created_by)} />
)}
)
}
}
In articleview2, I print all the data that is present in each of the articles along with the writer's name.
My articleview class is:
class ArticleView extends Component {
state = {article: this.props.article};
componentDidMount() {
console.log(this.props.writer_name;
}
render () {
return (
<React.Fragment>
<h2>{article.title}</h2>
<p>{article.body}</p>
<span>{this.props.writer_name}</span>
</React.Fragment>
)
}
}
If you see closely, I wrote two console.log statements to get the writer names. Based on the order, first the console log present in articleview class runs, which is undefined, and thenafter the data is fetched from the API call and the console log runs which returns the correct writer name.
I wanted to know, where is the error? Also, as I noticed, there are too many API calls being made to get the writer's name multiple time for all the listed articles. What are the industry best practices for these cases?
I want to know where is the error.
When you are writing this.state.articles.map(), means you're using property map of the Array articles which may be undefined before the data is fetched that will cause you the error Cannot read property map of undefined.
Solution
Now, as the API request is asynchronous, means render method will not wait for the data to come. So what you can do is use a loader variable in the state, and set it to true as long as the request is being made, and when the response has come, make it false, and show the loader in render when this.state.loader is true, and show articles when it is false.
Or you can initialize this.state.articles with an empty array that won't cause you the error.
Also, as I noticed, there are too many API calls being made to get the writer's name multiple time for all the listed articles. What are the industry best practices for these cases?
It is extremely bad practice to make an API request in the loop. Even myself has been scolded on it once I did it in my company.
Solution
You have tell your backend engineer to provide you filter for including the writer's name in each object of the article. We use Loopback on our backend, which provides a filter for including the related model in each object internally.
Since your API calls have a lot of things in common, you should first set up an axios instance that re-uses those common features:
const api = axios.create({
baseURL: 'http://localhost:8000/api/',
headers: { Authorization: `Bearer ${localStorage.token}` }
});
Now since your MainBody needs to fetch the resources from the API asynchronously, there will be a short period where the data is not yet available. There are two ways you can handle this. Either the MainBody can be responsible for making all the calls, or it can be responsible for just making the call to get all the articles, then each of the ArticleView components can be responsible for getting the writer's name. I'll demonstrate the first approach below:
class MainBody extends Component {
state = { articles: null, error: null, isLoading: true };
async componentDidMount () {
try {
const response = await api.get('articles/');
const articles = await Promise.all(
response.data.map(async article => {
const response = await api.get(`writer/${article.created_by}/`);
return { ...article, writer_name: response.data.name };
})
);
this.setState({ articles, isLoading: false });
} catch (error) {
this.setState({ error, isLoading: false });
}
}
render () {
const { articles, error, isLoading } = this.state;
return isLoading ? 'Loading...' : error
? `Error ${error.message}`
: articles.map(article => (
<ArticleView
key={article.id}
article={article}
writer_name={article.writer_name}
/>
)
);
}
}

Passing the response of an API call as a parameter for another API call

I'm trying to make an API call that has for parameter a state set by another call :
I'll explain my code below !
import React, { Component } from 'react';
import axios from 'axios';
class App extends Component {
constructor(props) {
super(props);
this.state = {
coinList: [],
coinInfos: []
};
}
componentDidMount() {
// FIRST CALL HERE: I get a list of every coin
axios.get('https://min-api.cryptocompare.com/data/all/coinlist')
.then(res => {
const coins = res.data;
console.log(coins);
this.setState({ coinList: coins.Data });
});
// SECOND CALL HERE: I want to get the price of every coin of the previous list
if (this.state.coinList == null) {
return null;
}
else {
axios.get('https://min-api.cryptocompare.com/data/pricemultifull?fsyms=' + this.state.coinList + '&tsyms=USD')
.then(response => {
const coinCap = response.data;
this.setState({ coinInfos: coinCap.RAW });
});
}
render() {
return(
<div className="App">
{Object.keys(this.state.coinInfos).map((key) => (
<div className="container">
<span className="left">{key}</span>
<span className="right">{this.state.coinInfos[key].USD.MKTCAP}</span>
</div>
))}
</div>
);
}
}
I'm using an if condition for my second call because this.state.coinList returns 2 empty arrays and 1 array in which are the data (I don't know why there are 2 empty arrays by the way).
This code works for the first call, but not for the second.
I'm a beginner in React so I looked at the doc, I think the problem is that the 1st call doesn't render before the second call, so this.state.coinList is empty.
Can you tell me if I'm wrong ? And if I'm true, where should I make my second API call ?
I hope I'm clear, thank you for your time !
Here is the API's doc if you need : https://www.cryptocompare.com/api/#-api-data-
The calls are synchronous. You get to the second call before it even finishes the first one. As the simplest solution I would suggest to put the second call in the then callback function of your first call.
In there you will have the response data of the first call for sure and then you can work with it and pass it to the second call.
componentDidMount() {
// FIRST CALL HERE: I get a list of every coin
axios.get('https://min-api.cryptocompare.com/data/all/coinlist')
.then(res => {
const coins = res.data;
console.log(coins);
this.setState({ coinList: coins.Data });
// SECOND CALL HERE: I want to get the price of every coin of the previous list
if (this.state.coinList == null) {
return null;
}
else {
axios.get('https://min-api.cryptocompare.com/data/pricemultifull?fsyms=' + this.state.coinList + '&tsyms=USD')
.then(response => {
const coinCap = response.data;
this.setState({ coinInfos: coinCap.RAW });
});
});
}
You are making asynchronous calls for operations you want to perform synchronously. Then First answer is correct but I would prefer to use async/await
// Put async before the componentDidMount so you can use async/await
async componentDidMount() {
// FIRST CALL HERE: I get a list of every coin
const resp1 = await axios.get(
"https://min-api.cryptocompare.com/data/all/coinlist"
);
const coins = resp1.data;
/* Placing the condition above the set state ensures
unnecessary setting of state and rending of components would not happen */
if (!coins) return;
this.setState({ coinList: coins });
// SECOND CALL HERE: I want to get the price of every coin of the previous list
const resp2 = axios.get(
`https://min-api.cryptocompare.com/data/pricemultifull?fsyms=${coins}&tsyms=USD`
);
this.setState({ coinInfos: resp2.data.RAW });
}

Set state once all data is pushed to array

I am trying to make a 'facebook kind of newsfeed' in React with Firebase Firestore. In the componentDidMount I first get the friendslist and per friend I will get their activities which I push to an empty array and sort() + reverse() the id's which are timestamps. This way the newest activity will be first in the array. Once ALL the items are pushed to the array, I want to set the state with the array. This is the code that I have:
import React, { Component } from 'react'
import { db, firebaseAuth } from '../../helpers/base'
import Activities from './Activities'
export default class ActivityList extends Component {
state = {
activityKeys: [],
}
componentDidMount(){
const uid = firebaseAuth().currentUser.uid
var activityKeys = []
db.doc(`users/${uid}/social/friends`).get().then( (doc) => {
//GET ALL THE FRIENDS
const friends = doc.data() //OBJECT OF {friendOneId: "friendOneId", friendTwoId: "friendTwoId"}
//LOOP THROUGH FRIENDS AND GET ACTIVITY
Object.keys(friends).forEach( friend => {
db.collection("activity").where("user", "==", friend).get().then((querySnapshot) => {
querySnapshot.forEach( (doc) => {
const activity = doc.id
activityKeys.push(activity)
activityKeys.sort().reverse()
})
})
})
})
console.log('activityKeys: ', activityKeys)
this.setState({ activityKeys })
}
render () {
return (
<div>
<h5>Activity List</h5>
<Activities activityKeys={this.state.activityKeys} />
</div>
)
}
}
The problem is that the array isn't set correctly or that it maybe is set before all the items are pushed. This is the log that I get:
It looks like it is loaded but it is empty between the brackets. If I console.log This.state.activity I get the same result. Can someone tell me how to fix this? And how can I setState once all the activities are pushed to the empty array?
You get a Promise when you make a call to API, but you set this.setState({ activityKeys }) before the call has completed. In other words, you must chain another .then() after the data has been received, in which you will call this.setState({ activityKeys }). What makes it a little difficult is that you're creating many Promises when iterating with forEach, and you need to wait for each of them to complete. You could save them all to list, and use Promise.all to wait for their completion and return it from the previous .then. Read more on the promises: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

Ajax request won't display in react render function

I don't know why the result of my axios promise doesn't show up in the render function. I'm using the create-react-app tools by the way.
_getPrice() {
const url = 'https://api.coinbase.com/v2/prices/BTC-USD/spot';
axios.get(url)
.then(function (response) {
//console.log(response.data.data.amount);
let prices = response.data.data.amount;
return prices;
})
}
render() {
return(<div><h3> {this._getPrice()} </h3></div>);
}
React only re-renders components when either the state or props of the component change. If data changes during the render cycle, but doesn't interact with those variables, then the changes will not show up.
You can save the result of your promise to state as follows:
getInitialState() {
return {prices: undefined}
}
componentDidMount() {
const url = 'https://api.coinbase.com/v2/prices/BTC-USD/spot';
axios.get(url)
.then(function (response) {
//console.log(response.data.data.amount);
let prices = response.data.data.amount;
this.setState({prices: prices});
}.bind(this))
}
render() {
return(<div><h3> {this.state.prices} </h3></div>);
}
first you cant call a function in return in render function and if you want update your view you must update state or props...
When requesting data to the server, the request is async, this means it will take time for the server to respond and the browser will continue the execution, than been said, in your current implementation you are returning a promise in your _getPrice function and then when the server responds you are not doing anything with the data.
The second problem is that react will only re-render the component when there are changes on the state or on the props, and in your current implementation you are not changing any of that.
Here's a sample of how you need to do it in order to make it work.
class YourComponent extends Component {
state = {
prices: 0,
};
componentDidMount() {
const url = 'https://api.coinbase.com/v2/prices/BTC-USD/spot';
axios.get(url)
.then((response) => {
let prices = response.data.data.amount;
this.setState({ prices });
});
}
render() {
const { prices } = this.state;
return(
<div>
<h3> {prices} </h3>
</div>
);
}
}
Good luck!

Resources