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

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()
}

Related

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

TypeError: this.state.tasks.map is not a function

I am getting the following error in my code. Can you please help me to understand the issue. I have included my page component and task list component.
TypeError: this.state.tasks.map is not a function
Page Show.js
import React, { Component } from 'react';
import axios from 'axios';
import TasksList from './TasksList';
export default class Show extends Component {
constructor(props) {
super(props);
this.state = {tasks: [] };
}
componentDidMount(){
axios.post('http://mohamed-bouhlel.com/p5/todolist/todophp/show.php')
.then(response => {
this.setState({ tasks: response.data });
})
.catch(function (error) {
console.log(error);
})
}
tasksList(){
return this.state.tasks.map(function(object,i){
return <TasksList obj = {object} key={i} />;
});
}
render() {
return (
<div>
{ this.tasksList() }
</div>
)
}
}
Page TasksList.js
import React, { Component } from 'react';
export default class TasksList extends Component {
render() {
return (
<div>
<div>{this.props.obj.task}</div>
</div>
)
}
}
Using a GET request and correct protocol (https vs http) seems to resolve the issue.
axios.get("https://mohamed-bouhlel.com/p5/todolist/todophp/show.php")
Response.data is not an array and basically you can't call map on a non-array.
I suggest console.log(response.data) to check the data type.
And I guess maybe you're doing a axios.post instead of a correct axios.get. log the response.data and you'll find out.

React: axios.interceptors do not work in hoc

axios.interceptors in hoc withErrorHandler work for clicked method in App.js, but do not work for componentWillMount or componentDidMount in App.js. How can I fix it?
App.js
class App extends Component {
componentDidMount() {
axios.get('https://wrongaddress')
.then(response => {
console.log(response);
});
}
clicked() {
axios.get('https://wrongaddress')
.then(response => {
console.log(response);
});
}
render() {
return (
<button onClick={this.clicked}>btn</button>
);
}
}
export default withErrorHandler(App, axios);
hoc/withErrorHandler.js
const withErrorHandler = ( WrappedComponent, axios ) => {
return class extends Component {
componentDidMount() {
axios.interceptors.request.use(req => {
console.log(req);
return req;
});
}
render() {
return (
<WrappedComponent {...this.props} />
);
}
}
};
You add the interceptor in the hoc just after the first render. And you use axios in componentWillMount in the App. componentWillMount is before the first render.
I suggest to place the axios call to componentDidMount in the App. It is recommended to put all side effects like load data to componentDidMount anyway. Check the documentation here: https://reactjs.org/docs/react-component.html#componentdidmount
class App extends Component {
componentdidMount() {
axios.get('https://wrongaddress')
.then(response => {
console.log(response);
});
}
...
Also you can move the interceptor handling in the HOC to componentWillMount.
const withErrorHandler = ( WrappedComponent, axios ) => {
return class extends Component {
componentWillMount() {
axios.interceptors.request.use(req => {
console.log(req);
return req;
});
}

React TypeError: Cannot read property 'map' of undefined on passing props

After get the comments array from post component and pass it to comments component
the logs start to show the error in the screenshot below
the components are:
import React, { Component } from "react";
import axios from "axios";
import Comments from "../components/comments";
class Article extends Component {
constructor(props) {
super(props);
this.state = {
title: "",
error: "",
comment: ""
};
}
componentDidMount() {
this.getComments();
}
getComments = () => {
const {
match: { params }
} = this.props;
return axios
.get(`/articles/${params.id}/comments`, {
headers: {
Accept: "application/json",
"Content-Type": "application/json",
}
})
.then(response => {
return response.json();
})
.then(response => this.setState({ comments: response.comments }))
.catch(error =>
this.setState({
error
})
);
};
render() {
return (
<div>
{this.state.title}
<div>
<h2>Comments</h2>
<Comments
getComments={this.getComments}
/>
</div>
</div>
);
}
}
export default Article;
and Comments component
import React, { Component } from "react";
import PropTypes from "prop-types";
import Comment from "./comment";
import axios from "axios";
import Article from "../screens/article";
class Comments extends Component {
constructor(props) {
super(props);
this.state = {
comments: [],
comment: "",
error: ""
};
this.load = this.load.bind(this);
this.comment = this.comment.bind(this);
}
componentDidMount() {
this.load();
}
load() {
return this.props.getComments().then(comments => {
this.setState({ comments });
return comments;
});
}
comment() {
return this.props.submitComment().then(comment => {
this.setState({ comment }).then(this.load);
});
}
render() {
const { comments } = this.state;
return (
<div>
{comments.map(comment => (
<Comment key={comment.id} commment={comment} />
))}
</div>
);
}
}
export default Comments;
so, I've tried to pass it by props, and set the state on comments component.
and instead of use just comments.map I've tried to use this.state but show the same error in the logs.
So, someone please would like to clarify this kind of issue?
seems pretty usual issue when working with react.
If an error occurs you do:
.catch(error => this.setState({ error }) );
which makes the chained promise resolve to undefined and that is used as comments in the Comments state. So you have to return an array from the catch:
.catch(error => {
this.setState({ error });
return [];
});
Additionally it woupd make sense to not render the Comments child at all if the parents state contains an error.
The other way is checking whether it’s an array and if so check it’s length and then do .map. You have initialized comments to empty array so we don’t need to check whether it’s an array but to be on safer side if api response receives an object then it will set object to comments so in that case comments.length won’t work so it’s good to check whether it’s an array or not.
Below change would work
<div>
{Array.isArray(comments) && comments.length>0 && comments.map(comment => (
<Comment key={comment.id} commment={comment} />
))}
</div>
The first time the comments component renders there was no response yet so comments were undefined.
import React, { Component } from "react";
import PropTypes from "prop-types";
import Comment from "./comment";
import axios from "axios";
import Article from "../screens/article";
class Comments extends Component {
constructor(props) {
super(props);
this.state = {
comments: [],
comment: "",
error: ""
};
this.load = this.load.bind(this);
this.comment = this.comment.bind(this);
}
componentDidMount() {
this.load();
}
load() {
return this.props.getComments().then(comments => {
this.setState({ comments });
return comments;
});
}
comment() {
return this.props.submitComment().then(comment => {
this.setState({ comment }).then(this.load);
});
}
render() {
const { comments } = this.state;
if (!comments) return <p>No comments Available</p>;
return (
<div>
{comments.map(comment => (
<Comment key={comment.id} commment={comment} />
))}
</div>
);
}
}
export default Comments;

ExceptionsManager.js:76 TypeError: this.state.albums.map is not a function

I kept getting this error on this line
return this.state.albums.map(album => album.title);
Which I don't think I did anything wrong there.
This is my entire code
import React, { Component } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import axios from 'axios';
class AlbumList extends Component {
state = { albums: [] };
componentWillMount() {
axios.get('https://rallycoding.herokuapp.com/api/music_albums')
.then(response => this.setState({ albums: response }));
}
renderAlbums() {
return this.state.albums.map(album => <Text>album.title</Text>);
}
render() {
// console.log(this.state);
return <View>{ this.renderAlbums() }</View>;
}
}
export default AlbumList;
Is there anything that I missed ?
axios.get yields a response object, not the contents of the response body. To access that, use response.data.
class AlbumList extends Component {
// ...
componentWillMount() {
axios.get('https://rallycoding.herokuapp.com/api/music_albums')
.then(response => this.setState({ albums: response.data }));
}
// ...
}
The fact that you were setting this.state.albums to the response is the reason why it's not an Array.

Categories