Get data properly from an API with React - javascript

I am using an API which contains cards. And to call this API, I am using Axios.
So far so good, but I want to return the deck_id and for some reason it does not work. And I get the error "this.state.card.map is not a function"
Here is my current code:
import React from "react";
import axios from "axios";
const CARD_API = "https://deckofcardsapi.com/api/deck/new/shuffle/";
export default class PersonList extends React.Component {
constructor(props) {
super(props)
this.state = {
card: []
}
}
async componentDidMount() {
const card= await axios.get(CARD_API)
this.setState({ card})
}
render() {
return (
<ul>
{this.state.card.map(card=>
<li>{card.deck_id}</li>
)}
</ul>
)
}
}

In axios you will get data inside response.data , so this is how you will get access to data
try {
const response = await axios.get(MOVIE_API); //<-- This will have a lot more thing apart from data you need
const movie = response.data; //<---- SOLUTION
this.setState({ movie })
} catch (err) {
console.log(err)
}
Note : Always put your async await code within try catch block if
possible

Related

Axios loading API, can't reference API in the DOM afterwards, "TypeError: this.state.products.map is not a function"

I am loading a local API using Axios. On the .then, if I console.log the result, I get the result just fine in my console.
I then assign it to state under the name 'products'. In the DOM, if I try and reference it, I get the error:
TypeError: this.state.products.map is not a function
What reason could there be for this? I wrapped the API call in a ComponentWillMount() and it still doesn't work.
Here is my code:
App.js
import axios from "axios"
import React from "react"
// const api = axios.create({
// baseURL: `http://localhost/api/`,
// })
class App extends React.Component {
constructor() {
super()
this.state = {
products: [],
}
}
componentDidMount() {
axios.get("http://localhost/api/").then((res) => {
console.log(res.data)
this.setState({ products: res.data })
})
}
render() {
return (
<div>
{this.state.products.map((product) => (
<div>
<h1>{product.id}</h1>
</div>
))}
</div>
)
}
}
export default App
Verify what you have in res.data: mb it's a JSON-string and you need to parse it first, or it's an object, but it should be array.

How to get state in react after render

I am trying to fetch data from firebase. I am able to get the data and update the state, but state returns undefined after render in my React context Provider. I have tried to use some of the Life cycle method like componentWillMount or calling my fetchData function my the constructor function , since it get called before render, but none is working. Below is my code.
import React, { Component } from 'react';
import { dataDB, productDetail } from './data';
import { db } from './config/fbConfig'
import { TimerSharp } from '#material-ui/icons';
const ProductContext = React.createContext();
class ProductProvider extends Component {
constructor(props) {
super(props)
this.state = {
products: []
}
this.fetchData()
}
fetchData = () => {
db.collection("projects")
.get()
.then(querySnapshot => {
const data = querySnapshot.docs.map(doc => doc.data());
console.log(data); //successfully returns the data
// this.setState({ projects: data });
this.setState(() => {
return {
projects: data
}
})
console.log(this.state.products) // successfully returns the data and update the state
});
}
render() {
console.log(this.state.products) // returns empty arr and I need it to return the updated state with data
return (
<ProductContext.Provider value={{
...this.state
}}>
{this.props.children}
</ProductContext.Provider>
)
}
}
const ProductConsumer = ProductContext.Consumer;
export { ProductProvider, ProductConsumer };
The issue is this.state.products get called before calling data in firebase. Please how can I be able to get data after render.
In fetchData() you set the attribute this.state.projects but in render you log this.state.products

Where should I call a method to use data from it?

I'd like to call getAlbums() method so I can use the data from the get request and display album data on the client side. I don't know where to call it though. I tried to call it in render() but it creates an infinite loop.
Albums.js
import React, { Component } from "react";
import { useParams } from "react-router-dom";
import axios from "axios";
import AlbumCard from "./AlbumCard";
export class Albums extends Component {
constructor(props) {
super(props);
this.state = { albums: [] };
this.getAlbums = this.getAlbums.bind(this);
}
async getAlbums() {
const {
match: { params },
} = this.props;
console.log(params.id);
try {
const res = await axios.get(
`http://localhost:4000/albums/${encodeURIComponent(params.id)}`,
{
params: {
id: params.id,
},
}
);
console.log(`Returned album data from the server: ${res}`);
this.setState({ albums: res.data });
} catch (err) {
console.log(err);
}
}
render() {
return (
<>
<div className="container" style={{ color: "white" }}>
hello
</div>
</>
);
}
}
export default Albums;
I wanna do something like this inside the div.
this.state.albums.map((album) => (<AlbumCard img={album.img}/>))
The reason you get an infinite loop is because you're calling setState in render. Here is what's happening behind the scenes:
1.getAlbums is called in the render method.
2.The function triggers setState.
3.setState causes re-render.
4.In the render method, getAlbums is called again.
Repeat 1-4 infinitely!
Here's is what you could do:
Create a button and bind getAlbums as a method to the onClick event handler.
2.Run getAlbums on ComponentDidMount like so:
componentDidMount() {
this.getAlbums();
}
componentDidMount() is the best place for making AJAX requests.
The componentDidMount() method will set state after the AJAX call fetches data. It will cause render() to be triggered when data is available.
Here is the working example with componentDidMount()
import React, { Component } from "react";
import { useParams } from "react-router-dom";
import axios from "axios";
import AlbumCard from "./AlbumCard";
export class Albums extends Component {
constructor(props) {
super(props)
this.state = { albums: [] }
}
componentDidMount() {
axios.get(
`http://localhost:4000/albums/${encodeURIComponent(this.props.id)}`,
{ params: { id: this.props.id } }
)
.then(response => {
console.log(`Returned album data from the server: ${res}`)
this.setState({ albums: response.data })
}
)
.catch(e => {
console.log("Connection failure: " + e)
}
)
}
render() {
return (
<div>
{/* Code for {this.state.albums.map(item => )} */}
{/* render() method will be called whenever state changes.*/}
{/* componentDidMount() will trigger render() when data is ready.*/}
</div>
)
}
}
export default Albums
More information:
https://blog.logrocket.com/patterns-for-data-fetching-in-react-981ced7e5c56/
use componentDidMount()
componentDidMount(){
getAlbums()
}

Why is my fetch promise being stored in the prop as empty instead of the json i'm fetching?

I'm trying to grab json from my backend to fill a table on the front end. Nothing is loading and in the react debugging tools it says the table prop is empty.
I've added async to the function that is doing the fetching, but it still seems to pass the json to the prop before its finished (not totally sure).
EDIT: lines are missing in the code because I cut out what was irrelevent
in app.js
import React, { Component } from 'react'
import Table from './Table'
class App extends Component {
render() {
const repos = getGitHubRepos()
return (
<div className="container">
<Table repoData={repos} />
</div>
)
}
}
async function getGitHubRepos() {
const response = await fetch('valid url i'm hiding')
return await response.json()
}
export default App
in table.js
import React, { Component } from 'react'
class Table extends Component {
render() {
const { repoData } = this.props
return (
<table>
<TableHeader />
<TableBody repoData={repoData} />
</table>
)
}
}
const TableBody = props => {
const rows = props.repoData.map((row, index) => {
return (
<tr key={index}>
<td>{row.name}</td>
<td>{row.lang}</td>
</tr>
)
})
return <tbody>{rows}</tbody>
}
export default Table
I expect the output to map each bit of json into the table but it isn't doing that because the prop is empty when it gets to table.js
You can perform async tasks in an async componentDidMount method:
class App extends Component {
constructor() {
super();
this.state = { repos: [] };
}
async componentDidMount() {
const repos = await getGitHubRepos();
this.setState({ repos });
}
render() {
return (
<div className="container">
<Table repoData={this.state.repos} />
</div>
);
}
}
async function getGitHubRepos() {
const response = await fetch("valid url Im hiding");
return response.json();
}
export default App;
Be adviced async/await syntax is not covered by all browsers
Move your fetching logic in one of Reacts life-cycle methods I would suggest componentDidMount, you should never fetch anything in the render method, you should even avoid extensive calculations there.. after you get the data I would save it in the local component state with this.setState({someState: data}) .. when the state is changed your component will automatically re render. You can read you data from you component state this this.state.someState
You get a thenable promise from fetch you can use .then(function () {}) to define what happens when the data is fetched
function getGitHubRepos() {
fetch('valid url i'm hiding')
.then(function (res) {
this.setState({data: res.data}); // or something similar
});
}

Undefined State when pulling data for mount

I'm pulling data from my my database which needs to be available prior to the mounting of the component in order for the page to be populated with the componentDidMount() lifecycle method. I've verified that if i remove the setState and console.log my data, it does fetch from the DB as expected, but when I try to assign the data to my state variable, it return a error stating Unable to get property 'setState' of undefined or null reference within my componentWillMount() lifecycle method. I've listed my ReactJS code below.
import React, { Component, PropTypes } from 'react';
import Picture from '../../components/picture.jsx';
import { browserHistory } from 'react-router';
export default class Products extends Component {
constructor(props) {
super(props);
this.state = {clothingData: ''};
}
componentWillMount(){
fetch('/t')
.then(function(result){
return result.json();
})
.then(function(re){
this.setState({ clothingData: re });
console.log(this.state.clothingData);
})
.catch(function(error){
console.log(error);
});
}
componentDidMount(){
//empty for now
}
render(){
var MyArray = ['justin','tiffany','joe','john','karissa','pam','joseph','sean','kim'];
var imageSrc = ['http://placehold.it/249x373','http://placehold.it/249x373','http://placehold.it/249x373','http://placehold.it/249x373','http://placehold.it/249x373',
'http://placehold.it/249x373', 'http://placehold.it/249x373', 'http://placehold.it/249x373'];
return (
<div>
<Picture src = {imageSrc} onClick = { () => {browserHistory.push('/Product'); }} name = {MyArray} amount = {8} />
</div>
);
}
}
The problem is that this is being reassigned from the component instance to the function instance/global object.
componentWillMount() {
fetch('/t')
.then((result) => {
return result.json();
})
.then((re) => {
this.setState({ clothingData: re });
console.log(this.state.clothingData);
})
.catch(function(error){
console.log(error);
});
}
will work just fine since the arrow function will ensure that the this is bound to the component instance so this.setState will actually be defined. Whereas what you have the this is being set to the global object which does not have a property of setState

Categories