So I'm new to React, and having trouble fetching API. I've successfully fetched data object(I've checked it with console.log(), but somehow cannot setState it. Please see the code below. It's my full code.
import React, { Component } from 'react';
import EachCake from './EachCake';
class Cake extends Component {
constructor(props){
super(props);
this.state = {
}
}
componentDidMount() {
this._fetchApiEachCake();
}
_renderEachCake = () => {
return <EachCake
image={this.cake_object.image}
source={this.cake_object.source}
body={this.cake_object.body}
/>
}
_fetchApiEachCake = () => {
return fetch("http://127.0.0.1:8000/api/cake/3")
.then((response) => response.json())
.then(data => console.log(data))
.then(data => this.setState({cake_object : data}))
// .catch((err) => console.log(err))
}
render() {
return (
<div>
{this.state.cake_object ? this._renderEachCake() : "Loading this cake"}
</div>
)
}
}
export default Cake
For some reason, all I get on the screen is "Loading this cake". What do you think is the problem?
import React, { Component } from 'react';
import EachCake from './EachCake';
class Cake extends Component {
constructor(props){
super(props);
this.state = {
// 🔥 state initialization is optional also, useful for default values
}
}
componentDidMount() {
this._fetchApiEachCake();
}
_renderEachCake = () => {
return (
<EachCake
image={this.state.cake_object.image} // 🌟🌟
source={this.state.cake_object.source}
body={this.state.cake_object.body}
/>
)
}
_fetchApiEachCake = () => {
// 🔥 you can also remove return here
return fetch("http://127.0.0.1:8000/api/cake/3")
.then((response) => response.json())
.then(data => console.log(data) || data) // 🌟
.then(data => this.setState({cake_object : data}))
// .catch((err) => console.log(err))
}
render() {
return (
<div>
{this.state.cake_object ? this._renderEachCake() : "Loading this cake"}
</div>
)
}
}
export default Cake
🌟🌟 must be grabbed from the state not directly from this reference.
🌟 console.log doesn't return anything, so you must return data yourself oق combine setState and logging step both in one step e.g.
.then(cake_object => console.log(cake_object) || this.setState({ cake_object }))
The then() method returns a Promise.
if you are trying to check if the data is loaded or not you should use the callback this.setstate({key: value}, () => {//do something})
you can use this to set a flag whether data has been loaded into state or not. and i also think that you should initialize that cake_object to null.
so after that your code would be like:
this.state = {
loaded: false,
cake_object: null
}
_fetchApiEachCake = () => {
return fetch("http://127.0.0.1:8000/api/cake/3")
.then((response) => response.json())
.then(data => console.log(data))
.then(data => this.setState({cake_object : data}, () => {
console.log(this.state.cake_object);
this.setState({loaded: true});
}))
// .catch((err) => console.log(err))
}
render() {
return (
<div>
{this.state.loaded ? this._renderEachCake() : "Loading this cake"}
</div>
)
}
2 changes :
1.
this.state = {
cake_object:null,
}
_fetchApiEachCake = () => {
return fetch("http://127.0.0.1:8000/api/cake/3")
.then((response) => response.json())
.then((data) => {
console.log(data)
this.setState({cake_object : data})
})
// .catch((err) => console.log(err))
}
Hopefully it works!
Related
import React, { Component } from 'react';
import { render } from 'react-dom';
import Details from './Details';
import './index.css';
class App extends Component {
constructor() {
super();
this.state = {
usersData: [],
error: null,
selectedUser: null,
};
}
handleClick = (id) => {
const selectedUser = this.state.usersData.find(user => user.id === id)
this.setState(() => ({ selectedUser }))
}
findAlbum = (selectedUser, id) => {
fetch(`https://jsonplaceholder.typicode.com/users/${id}/albums`)
.then(response => response.json())
.then(data => {
selectedUser.albums = data;
})
.catch(err => this.setState({ error: err.message }))
}
setAlbum = (id) => {
const selectedUser = this.state.usersData.find(user => user.id === id)
this.findAlbum(selectedUser, id);
}
render() {
this.state.usersData.forEach(user => {
this.setAlbum(user.id)
})
const usersList = this.state.usersData.map(user => {
return <li key={user.id} onClick={() => this.handleClick(user.id)}>{user.name} {user.username} {user.email} {user.albums.size}</li>
})
return (
<>
<div className="UserList">
<h1>Users</h1>
<ul>{usersList}</ul>
</div>
<Details user={this.state.selectedUser} />
</>
);
}
componentDidMount() {
fetch('https://jsonplaceholder.typicode.com/users')
.then(response => response.json())
.then(data => {
this.setState({ usersData: data })
})
.catch(err => this.setState({ error: err.message }))
}
}
render(<App />, document.getElementById('root'));
When i am doing console.log selecteduser.albums inside findalbum function it is showing the data but when i did it in the li line it shows cant read size of undefined
i need to create a react app which shows list of users and their name username and email corresponding to each user we have albums we need to show album number also but i cant do it
is there any problem with pass by value or pass by reference in JS?
It's very wrong to update the state directly in react. Instead, use setState with a map and change anything related to a substate there. Also a tip, instead of passing the Id in the forEach and finding the user, just pass the user
Because I'm new to using axios so I usually have a trouble in using it. Specifically, I'm making a react-infinite-scroll feature now, but when I compare its speed with other site, my post(react-infinite-scroll feature) is gonna be shown slowly a little. Then I'm thinking this problem is caused by 2 reasons
1. I'm not using axios properly
2. There is a thing makes axios speed urgrade, but I'm not using it
Here's my code, please give me some advice to increase my http request speed.
Thank you for reading my question!
class MainPage extends Component {
constructor(props) {
super(props)
axios.get("http://127.0.0.1:8000/api/question")
.then(res => {
this.setState({
AnswerPostMultiList: res.data
})
}
)
.catch(err => {
console.log(err)
})
}
state = {
AnswerPostMultiList : []
}
componentDidMount() {
window.addEventListener("scroll", this.handleScroll);
}
componentWillUnmount() {
window.removeEventListener("scroll", this.handleScroll);
}
handleScroll = () => {
console.log("scroll is executing")
const { innerHeight } = window;
const { scrollHeight } = document.body;
const scrollTop =
(document.documentElement && document.documentElement.scrollTop) ||
document.body.scrollTop;
if (scrollHeight - innerHeight - scrollTop < 1000 && !this.props.isLoading["isLoading"]) {
this.props.onIsLoading() #To prevent this code from calling back continuously, change the value of this.props.isLoading["isLoading"] to false
axios.get("http://127.0.0.1:8000/api/question")
.then(res => {
this.setState({
AnswerPostMultiList: this.state.AnswerPostMultiList.concat(res.data)
})
this.props.onIsLoading() #change the value of this.props.isLoading["isLoading"] to true
}
)
.catch(err => {
console.log(err)
})
}
};
render() {
return(
<>
<PageHeader />
<div className="find_members">
{ this.state.AnswerPostMultiList.map((answerpost,index) => {
return <AnswerPostMulti question={answerpost.question_text} q_owner={answerpost.question_owner} answer={answerpost.answer_image} a_owner={answerpost.answer_owner} key={index} />
})
}
</div>
</>
)
}
}
const mapDispatchToProps = (dispatch) => ({
onIsLoading: () => {
dispatch(isLoadingActions.isLoading())
}
})
const mapStateToProps = state => ({
isLoading: state.isLoading
})
export default connect(mapStateToProps, mapDispatchToProps)(MainPage)
The best place to call a axios API calls is at componentDidMount(){}.
The Application loading process will be in this order skeleton->Initial rendering->later componentDidMount method is called. So here your app skeleton will be loaded and after that you can fetch data and use it to your app skeleton.
componentDidMount() {
axios.get("http://127.0.0.1:8000/api/question")
.then(res => {
this.setState({
AnswerPostMultiList: res.data
})
}
)
.catch(err => {
console.log(err)
});
}
So, when I click on the add to cart button on the Screen2, it logs articleToCart aka cartArticle as empty array... Only when I go back to Screen1 and than again on the Screen2, pressing add to cart button again it logs cartArticle array with one item even though add to cart button was clicked 2x... How can I make it that when I click on add to cart button, it updates the state immediately? What am I doing wrong? I am using react navigation v2. Is it possible to setState trough params and that to be instant not like this, with delay?
class Screen1 extends Component {
state = {
articles: {
article: [],
},
cartArticle: []
};
articleToCart = () => {
this.setState(prevState => {
return {
cartArticle: prevState.cartArticle.concat(prevState.articles.article)
};
});
};
qrCodeOnReadHandler = ({ data }) => {
fetch(data)
.then(response => response.json())
.then(json => [
console.log(json),
this.setState({
...this.state,
articles: {
...this.state.articles,
article: json[0],
}
}),
this.props.navigation.navigate("Screen2", {
addToCartOnPress: () => this.articleToCart(),
articleToCart: this.state.cartArticle,
})
])
.catch(err => {
alert("Nesto ne valja. Pokusajte ponovo!");
console.log(err);
});
};
render() {
return (
);
}
}
Second screen
class Screen2 extends Component {
addToCartHandler = () => {
const { navigation } = this.props;
const articleToCart =navigation.getParam("articleToCart","Nedostupno");
const add = navigation.getParam("addToCartOnPress", "Nedostupno");
console.log(articleToCart);
add();
};
goBackHandler = () => {
this.props.navigation.goBack();
};
render() {
return (
<View style={styles.buttons}>
<CustomButton color="#1DA1F2" onPress={this.goBackHandler}>
Back
</CustomButton>
<CustomButton color="#1DA1F2" onPress={this.addToCartHandler}>
Add to Cart
</CustomButton>
);
}
}
in your qrCodeOnReadHandler on screen1:
[
console.log(json),
this.setState({
...this.state,
articles: {
...this.state.articles,
article: json[0],
}
}),
this.props.navigation.navigate("Screen2", {
addToCartOnPress: () => this.articleToCart(),
articleToCart: this.state.cartArticle,
})
]
you are returning an array with your functions as its indices.
try changing it to this instead.
{
console.log(json);
this.setState({
...this.state,
articles: {
...this.state.articles,
article: json[0],
}
});
this.props.navigation.navigate("Screen2", {
addToCartOnPress: () => this.articleToCart(),
articleToCart: this.state.cartArticle,
})
}
setState is async, you can't read values just after setting them .. but you can use setState callback, sth. like this:
.then(json => {
console.log(json)
this.setState({
...this.state,
articles: {
...this.state.articles,
article: json[0],
}
}, () => {
this.props.navigation.navigate("Screen2", {
addToCartOnPress: () => this.articleToCart(),
articleToCart: this.state.cartArticle,
})
})
})
I am having difficulties mapping over this object in order to render the data on the API.
Anyone has an idea of what I may be doing wrong?
class HomePage extends Component {
state = {
weatherResults: []
};
componentDidMount() {
let obj;
fetch(`http://api.openweathermap.org/data/2.5/forecast?
id=52490&appid=${API_KEY}&q=new%20york&cnt=2`)
.then(res => res.json())
.then(results => (obj = results))
.then(() => console.log(obj));
this.setState({
weatherResults: this.state.weatherResults
});
}
render() {
return (
<div>
{this.state.weatherResults &&
this.state.weatherResults.map(data => (
<div className="container">
<p>{data.city.name}</p>
</div>
))}
</div>
);
}
}
export default HomePage;
Since the fetch request is asynchronous, you want to use setState when the request has finished to put the response in the component state.
Looking at one of the sample requests of the API it looks like you get an object as response, which has city.name in it. Instead of having weatherResults be an array, you can set it to null initially and access city.name on it when the object has loaded.
Example
class HomePage extends Component {
state = {
weatherResults: null
};
componentDidMount() {
fetch(`http://api.openweathermap.org/data/2.5/forecast?id=52490&appid=${API_KEY}&q=new%20york&cnt=2`)
.then(res => res.json())
.then(results => {
this.setState({
weatherResults: results
});
})
.catch(error => console.error(error));
}
render() {
const { weatherResults } = this.state;
if (weatherResults === null) {
return null;
}
return (
<div>
<div className="container">
<p>{weatherResults.city.name}</p>
</div>
</div>
);
}
}
I am writing a metrics page using React-Redux, which I haven't used before, and am having trouble structuring it.
The basic structure is something like this:
<input id=start_date />
<input id=end_date />
<button id=submit onClick={ this.props.fetchChartData() }/>
<Chart1 />
<Chart2 />
The store structure is this:
dates
start_date: "2016-09-16"
end_date: "2016-09-16"
charts
Chart1
api_func: "get_supported_events"
fetching: false
fetched: false
data: null
error: null
Chart2
api_func: "get_events_closed"
fetching: false
fetched: false
data: null
error: null
Using thunk, my actions right now include these functions:
function getStateURL(state){
return state.charts.Chart1['api_func'];
}
export function fetchChartData(){
return (dispatch, getState) => {
dispatch(fetchChartDataStart());
return fetch(getStateURL(getState()))
.then((response) => response.json())
.then((json) => dispatch(receiveChartData(json)))
.catch((err) => dispatch(fetchChartDataError(err)));
}
}
The problem is, I don't want to hard code the chart name because I feel like I should be able to write one action since all of the charts need to do the same thing.
The best solution I could guess is to have the button trigger an event that the chart components could listen for so that when the state is requested it is limited to the chart's portion, not the entire state. Is there a way to make a react component trigger an event that can be caught by other components?
The solution you are proposing seems more like old Flux model when store was just an instance of EventEmitter.
Using Flux, you can make <Chart /> like
class Chart extends Component {
componentDidMount() {
store.addEventListener('fetchData', this.fetchData);
}
componentWillUnmount() {
store.removeEventListener('fetchData', this.fetchData);
}
this.fetchData() {
api.fetchChartData(store.get('chart1.url');
}
render() {
...
}
}
With Redux however it is not immediately obvious. But it is possible to do it:
class Chart1 extends Component {
componentWillReceiveProps(nextProps) {
if (!nextProps.fetching && !nextProps.fetched) {
const { fetchData, url } = this.props;
fetchData(url);
}
}
render() {
...
}
}
export default connect(state => ({
fetching: state.Chart1.fetching
fetched: state.Chart1.fetched
url: state.Chart1.url
}), {
fetchData
})(Chart1)
and in /action.js
export function fetchChartData(url){
return (dispatch) => {
dispatch(fetchChartDataStart());
return fetch(url)
.then((response) => response.json())
.then((json) => dispatch(receiveChartData(json)))
.catch((err) => dispatch(fetchChartDataError(err)));
}
}
Considering the similar functionalities in all the <Chart /> components, it's worth implementing Higher order component for this and keep url somewhere as constants rather than in store.
export const fetchData = (url) => (Wrapped) => {
class Wrapper extends Component {
componentWillReceiveProps(nextProps) {
if (!nextProps.fetching && !nextProps.fetched) {
const { fetchData, url } = this.props;
fetchData(url);
}
}
render() {
return <Wrapped {...this.props} />
}
}
return connect(null, { fetchData })(Wrapper);
}
In Chart.jsx use it like:
import { chart1Url } from '.../someconstants';
import { fetchData } from '/hocs/fetchData'
const Chart1 = () => {
return <div>...</div>;
}
export default fetchData(chartUrl)(Chart1);
Although it is possible, I still think the best solution would be to store URLs in a constants file, and put api functions in another module. You can do something like:
./api/fetchData.js
export function fetchData(url) {
return new Promise((resolve, reject) =>
fetch(url)
.then((response) => response.json())
.then((json) => resolve(json))
.catch((err) => reject(err));
}
./actions.js
import { fetchData } from '../api/fetchData';
import { urls } from '.../constants';
export function fetchChartData(){
return (dispatch) => {
dispatch(fetchChartDataStart());
return Promise.all(urls.map((url) =>
fetchData(url)
.then((json) => dispatch(receiveChartData(json)))
.catch((err) => dispatch(fetchChartDataError(err))));
}
}