Fetch is still pending in react api call - javascript

I have a react component. If I user clicks on a Link To on the parent component they land on this child componented just fine. However if they refresh the page, or go to the link directly, their is no data, so I need to make an api call again myself for that unique id.
When I make the api call (when issue is undefined), it works, but I get a promise back that has fulfilled, with no data. How do I get the object?
class Issue extends React.Component {
getIssue = async (id) => {
try {
const endpoint = `https://api.github.com/repos/facebook/create-react-app/issues/${id}`;
const response = await fetch(endpoint);
const data = await response.json();
return data;
} catch (error) {
console.log(error);
}
}
// }
render(){
let { issue } = this.props.location;
console.log(issue);
if(issue === undefined){
console.log('No Data');
issue = this.getIssue(this.props.match.params.id);
console.log(issue);
} else {
console.log('Data');
}
return (
<h1>ff</h1>
)
}
}

Rewrite your component to use state, rewrite getIssue to use Promise (it fits better here) and move it to componentDidMount method.
class Issue extends React.Component {
constructor(props) {
super(props);
this.state = {
data: null
};
}
componentDidMount() {
const id = this.props.match.params.id;
const endpoint = `https://api.github.com/repos/facebook/create-react-app/issues/${id}`;
fetch(endpoint)
.then(response => response.json())
.then(data => this.setState({ data }))
.catch(err => console.log(err));
}
render() {
return <h1>{this.state.data ? this.state.data.title : "Loading..."}</h1>;
}
}

The reason is because your getIssue() is async function and it will return a promise which you have to handle later on. In your render() method you are not doing it, you have to use getIssue() with then() chained where you can get your data from promise:
render() {
let { issue } = this.props.location;
console.log(issue);
if(issue === undefined) {
console.log('No Data');
this.getIssue(this.props.match.params.id)
.then(response => {
console.log(response);
});
} else {
console.log('Data');
}
return (
<h1>ff</h1>
)
}

You cannot call async functions during render()
You should do async calls on a useEffect hook (if using hooks) or componentDidMount or one of the lifecycles of a React class.
An example:
class Issue extends React.Component {
constructor() {
this.state = {
issue: null
}
componentDidMount() {
const { issue } = this.props.location;
if (!issue) {
getIssue(this.props.match.params.id).then(data => {
this.setState({ issue: data });
}
}
getIssue = async (id) => {
try {
const endpoint = `https://api.github.com/repos/facebook/create-react-app/issues/${id}`;
const response = await fetch(endpoint);
const data = await response.json();
return data;
} catch (error) {
console.log(error);
}
}
// }
render(){
let { issue } = this.state;
console.log(issue);
return (
<h1>ff</h1>
)
}
}

Two issues :
Fetch usage - Just use the direct result and not the data as follows:
const url = 'https://api.github.com/repos/facebook/create-react-app/issues/20'
const getData = async () => {
const data = await fetch(url);
console.log(data)
}
getData();
Using async in useEffect or componentDidMount - To use in useEffect you can refer this: How to call an async function inside a UseEffect() in React?

Related

Waiting for async function in React component & Showing Spinner

Beginner here.
Trying to fetch some data from a server and display it in my react component once its fetched.
However, I am having trouble integrating the async function into my react component.
import React, { useState } from "react";
import { request } from "graphql-request";
async function fetchData() {
const endpoint = "https://localhost:3090/graphql"
const query = `
query getItems($id: ID) {
item(id: $id) {
title
}
}
`;
const variables = {
id: "123123123"
};
const data = await request(endpoint, query, variables);
// console.log(JSON.stringify(data, undefined, 2));
return data;
}
const TestingGraphQL = () => {
const data = fetchData().catch((error) => console.error(error));
return (
<div>
{data.item.title}
</div>
);
};
export default TestingGraphQL;
I'd like to simply show a spinner or something while waiting, but I tried this & it seems because a promise is returned I cannot do this.
Here you would need to use the useEffect hook to call the API.
The data returned from the API, I am storing here in a state, as well as a loading state to indicate when the call is being made.
Follow along the comments added in between the code below -
CODE
import React, { useState, useEffect } from "react"; // importing useEffect here
import Layout from "#layouts/default";
import ContentContainer from "#components/ContentContainer";
import { request } from "graphql-request";
async function fetchData() {
const endpoint = "https://localhost:3090/graphql"
const query = `
query getItems($id: ID) {
item(id: $id) {
title
}
}
`;
const variables = {
id: "123123123"
};
const data = await request(endpoint, query, variables);
// console.log(JSON.stringify(data, undefined, 2));
return data;
}
const TestingGraphQL = () => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
// useEffect with an empty dependency array works the same way as componentDidMount
useEffect(async () => {
try {
// set loading to true before calling API
setLoading(true);
const data = await fetchData();
setData(data);
// switch loading to false after fetch is complete
setLoading(false);
} catch (error) {
// add error handling here
setLoading(false);
console.log(error);
}
}, []);
// return a Spinner when loading is true
if(loading) return (
<span>Loading</span>
);
// data will be null when fetch call fails
if (!data) return (
<span>Data not available</span>
);
// when data is available, title is shown
return (
<Layout>
{data.item.title}
</Layout>
);
};
since fetchData() returns a promise you need to handle it in TestingGraphQL. I recommend onComponentMount do your data call. Setting the data retrieved into the state var, for react to keep track of and re-rendering when your data call is finished.
I added a loading state var. If loading is true, then it shows 'loading' otherwise it shows the data. You can go about changing those to components later to suit your needs.
See the example below, switched from hooks to a class, but you should be able to make it work! :)
class TestingGraphQL extends Component {
constructor() {
super();
this.state = { data: {}, loading: true};
}
//when the component is added to the screen. fetch data
componentDidMount() {
fetchData()
.then(json => { this.setState({ data: json, loading: false }) })
.catch(error => console.error(error));
}
render() {
return (
{this.state.loading ? <div>Loading Spinner here</div> : <div>{this.state.data.item.title}</div>}
);
}
};

setState Does Not Define State Variable (ReactJS)

I am a noob of ReactJS and I created a Homepage for Users to view after logging in or registering . I am getting the data from my custom API(NodeJS) but once I fetch the data and attempt to setState it appears that it is not updating the state variables.
Here is my code.
export class SideMenu extends Component {
constructor(props) {
super(props)
this.state = {
user: {},
};
}
componentDidMount() {
this.fetchData();
console.log(this.user);
}
fetchData = () => {
axios.get("http://localhost:3001/returningusers").then((response) => {
const data = response.data.User[0]
console.log(data)
this.setState({
user: data
})
})
}
I really need help with this, it has slowed down my momentum and I have no idea how to fix it. Thanks.
I'm not so sure that's the correct syntax for an ES6 class method. How about trying the below instead of the arrow function:
fetchData() {
axios.get("http://localhost:3001/returningusers").then((response) => {
const data = response.data.User[0]
console.log(data)
this.setState({
user: data
})
})
}
Also you need to return Promises and either await or then them. For example:
async componentDidMount() {
await this.fetchData(); // you need to wait until this finishes
console.log(this.state.user);
}
// and you need to return the Promise here
fetchData() => {
return axios.get("http://localhost:3001/returningusers").then((response) => {
const data = response.data.User[0]
console.log(data)
this.setState({
user: data
})
})
}

Get undefined when trying to read response returned from API in react js [duplicate]

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 3 years ago.
In React ... I am trying to read the response return from API and get undefined, what is the problem?
Undefined occurs when calling the function retrieveItems() from the component.
**// item service class**
import axios_o from 'axios';
class ItemService {
retrieveItems() {
axios_o.get("https://jsonplaceholder.typicode.com/posts")
.then(response => {
return response;
}).catch();
}
}
**// component calling the item service**
import React from 'react'
import ItemService from "../Services/ItemService";
class Posts extends React.Component {
constructor(props) {
super(props);
}
componentDidMount = () => {
this.itemservice=new ItemService();
**console.log(this.itemservice.retrieveItems())**
}
render() {
return (
<h1>Posts List</h1>
);
}
}
export default Posts;
class ItemService {
retrieveItems() {
return axios_o.get("https://jsonplaceholder.typicode.com/posts")
.then(response => response)
.catch(error => error)
}
}
componentDidMount = () => {
this.itemservice=new ItemService();
this.itemservice.retrieveItems().then(res=>{
console.log(res);
}).catch(error=>{
console.log(error)
});
}
As I mentioned in the comment the method retrieveItems is not returning a value. To fix this return the axios call
retrieveItems() {
return axios_o.get("https://jsonplaceholder.typicode.com/posts")
.then(response => {
return response;
}).catch(
);
}
or rewrite it to async/await for better readability
async retrieveItems() {
try {
return await axios_o.get("https://jsonplaceholder.typicode.com/posts")
}catch(e) {
// do some error handling or move the try/catch to caller side
}
}
Now in your console log you should see not the real response of the API call but a Promise. To get the real response you also have to wait for the answer on caller side:
class Posts extends React.Component {
constructor(props) {
super(props);
}
componentDidMount = () => {
this.retrieveItems()
}
retrieveItems = async () => {
this.itemservice=new ItemService();
const response = await this.itemservice.retrieveItems()
console.log(response)
}
render() {
return (
<h1>Posts List</h1>
);
}
}
With this you should see the response in the console log.
The issue is the typical pitfall of wanting to return something from within a callback function to the outer function. That's can't work, because the outer function (retrieveItems) has already finished. You need to stay in the asynchronous pattern. The easiest is probably this:
import axios_o from 'axios';
class ItemService {
retrieveItems() {
return axios_o.get("https://jsonplaceholder.typicode.com/posts");
}
}
import React from 'react'
import ItemService from "../Services/ItemService";
class Posts extends React.Component {
componentDidMount = () => {
this.itemservice = new ItemService();
this.itemservice.retrieveItems().then((res) => {
console.log(res);
});
}
render() {
return (<h1>Posts List</h1>);
}
}
export default Posts;

Mobx, React Native, strange behavior of mobx mutation state after fetch data from API

I have React Native app with MobX store. And i use useEffect hook to call fetch action from MobX to get data from API. The rendering is pretty strange. It looks like this:
useEffect call MobX action with fetch -> loading data, but can not render, the loading is not stopping -> push the button and change the navigation stack -> the data is appearing on a previous screen where before it could not rendered -> come back to the previous screen and see the data that before could not came.
It means only when the navigation stack is changing the data is rendering. It looks like a problem with change MobX state. Can you help me please.
MobX state:
import { createContext } from 'react'
import { action, decorate, observable, computed, runInAction } from 'mobx'
import fetchData from '../utils/fetchData'
import mapObjects from '../utils/mapObjects'
class DataStore {
data = null
error = false
loading = true
get getData(){
return this.data
}
get getError(){
return this.error
}
get getLoading(){
return this.loading
}
async fetchData(url) {
this.data = null
this.error = false
this.loading = true
try {
console.log('TRY')
const response = await fetch(url)
const jsonResponse = await response.json()
const obj = await mapObjects(jsonResponse)
runInAction(() => {
console.log('WRITE!!!')
this.loading = false
this.data = obj
})
} catch (err) {
runInAction(() => {
console.log(err)
this.loading = false
this.error = err
})
}
}
}
decorate(DataStore, {
data: observable,
error: observable,
loading: observable,
fetchData: action
})
export default createContext(new DataStore())
Render component:
import React, { useContext, useEffect, useState } from 'react'
import { ActivityIndicator, FlatList, Platform, StyleSheet, View } from 'react-native'
import DataStore from '../mobx/DataStore'
import { autorun } from 'mobx'
import { ChartsHeader, CryptoItem, IconsHeader, ProjectStatusBar } from '../components'
import { useFetch } from '../hooks/useFetch'
import { WP, HP } from '../constants'
const styles = StyleSheet.create({
container: {
flex: 1
}
})
const ChartsScreen = ({ navigation }) => {
const { container } = styles
const store = useContext(DataStore)
const url = 'https://poloniex.com/public?command=returnTicker'
console.log('store', store)
useEffect(() => {
store.fetchData(url)
}, [])
//*Call custom hook and data distruction
//const { data, error, loading } = useFetch(url)
//*Change percent amount color depends on the amount
const percentColorHandler = number => {
return number >= 0 ? true : false
}
return (
<View style={container}>
{Platform.OS === 'ios' && <ProjectStatusBar />}
<IconsHeader
dataError={store.error}
header="Charts"
leftIconName="ios-arrow-back"
leftIconPress={() => navigation.navigate('Welcome')}
/>
<ChartsHeader />
<ActivityIndicator animating={store.loading} color="#068485" style={{ top: HP('30%') }} size="small" />
<FlatList
data={store.data}
keyExtractor={item => item.key}
renderItem={({ item }) => (
<CryptoItem
name={item.key}
highBid={item.highestBid}
lastBid={item.last}
percent={item.percentChange}
percentColor={percentColorHandler(item.percentChange)}
/>
)}
/>
</View>
)
}
export { ChartsScreen }
In my case it was, because i put all fetch functions to the one hook and call it in useEffect. In the end i have found the decision. I changes my function component to the class component and split all fetch functions in MobX store. Maybe it will be helpful for somebody:
MobX store:
import { action, observable, runInAction } from 'mobx'
class DataStore {
#observable data = null
#observable error = false
#observable fetchInterval = null
#observable loading = false
//*Make request to API
#action.bound
fetchInitData() {
const response = fetch('https://poloniex.com/public?command=returnTicker')
return response
}
//*Parse data from API
#action.bound
jsonData(data) {
const res = data.json()
return res
}
//*Get objects key and push it to every object
#action.bound
mapObjects(obj) {
const res = Object.keys(obj).map(key => {
let newData = obj[key]
newData.key = key
return newData
})
return res
}
//*Main bound function that wrap all fetch flow function
#action.bound
async fetchData() {
try {
runInAction(() => {
this.error = false
this.loading = true
})
const response = await this.fetchInitData()
const json = await this.jsonData(response)
const map = await this.mapObjects(json)
const run = await runInAction(() => {
this.loading = false
this.data = map
})
} catch (err) {
console.log(err)
runInAction(() => {
this.loading = false
this.error = err
})
}
}
//*Call reset of MobX state
#action.bound
resetState() {
runInAction(() => {
this.data = null
this.fetchInterval = null
this.error = false
this.loading = true
})
}
//*Call main fetch function with repeat every 5 seconds
//*when the component is mounting
#action.bound
initInterval() {
if (!this.fetchInterval) {
this.fetchData()
this.fetchInterval = setInterval(() => this.fetchData(), 5000)
}
}
//*Call reset time interval & state
//*when the component is unmounting
#action.bound
resetInterval() {
if (this.fetchInterval) {
clearTimeout(this.fetchInterval)
this.resetState()
}
}
}
const store = new DataStore()
export default store

How to access state value from one component to other function(not component) which is in separate file? React js

This is my Home Component:
import React from 'react';
import { getData } from '../../../util/network';
export default class Home extends React.Component {
constructor(props) {
super(props);
this.state = {
page: 1,
};
}
async componentWillMount() {
const val = await getData();
}
render() {
return() {
// jsx stuffs
}
}
}
This is a file called network.js: // which is a function
export const getData = () => {
const { page } = this.state; // this is undefined now
const url = `http://randomuser.in/${page}`;
fetch(url)
.then(res => res.json())
.then(res => {
return res;
})
.catch(error => {
console.log('error:', error);
});
};
How to access state value of page in my network.js file?
You should pass page state as a parameter to your function :
async componentDidMount() {
const val = await getData(this.state.page);
}
Note that I replaced componentWillMount by componentDidMount which is prefered for doing async actions.
export const getData = (page) => {
const url = `http://randomuser.in/${page}`;
fetch(url)
.then(res => res.json())
.then(res => {
return res;
})
.catch(error => {
console.log('error:', error);
});
};
You shouldn't be depending on this.state in your function. Its not a good practice. You should just pass the parameters/arguments you will need in that function.
Example
const val = await getData(this.state.page);
export const getData = (page) => {
// use page argument that passed
//...
};

Categories