How to handle fetch API AJAX response in React - javascript

I am making a simple AJAX request with the fetch API in React, specifically in the componentDidMount() function.
It is working, because the console appears to be logging the result. However, I don't know how to access the response...
componentDidMount = () => {
let URL = 'https://jsonplaceholder.typicode.com/users'
fetch(URL)
.then(function(response) {
let myData = response.json()
return myData;
})
.then(function(json) {
console.log('parsed json', json)
})
.catch(function(ex) {
console.log('parsing failed', ex)
})
} // end componentDidMount
I tried accessing myData outside of the fetch method, but this throws an error saying that it is undefined. So it is only accessible within the scope of the function.
I then tried this:
.then(function(response) {
let myData = response.json()
// return myData;
this.setState({
data: myData
})
})
This time, I get Cannot read property 'setState' of undefined(…)
How do I pass the fetch response to the state, or even just a global variable?
UPDATE
import React, { Component } from 'react';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
data: null
}
}
componentDidMount() {
let URL = 'https://jsonplaceholder.typicode.com/users'
fetch(URL)
.then( (response) => {
let myData = response.json()
// return myData;
this.setState({
data: myData
})
})
.then( (json) => {
console.log('parsed json', json)
})
.catch( (ex) => {
console.log('parsing failed', ex)
})
console.log(this.state.data)
} // end componentDidMount
render() {
return (
<div className="App">
{this.state.data}
</div>
);
}
}
export default App;

You have two issues as far as I can see, response.json() returns a promise, so you don't wanna set myData to the promise, instead first resolve the promise and then you can access your data.
Second, this is not in the same scope inside your fetch request, so that's why you are getting undefined, you can try saving the scope of this outside fetch:
var component = this;
fetch(URL)
.then( (response) => {
return response.json()
})
.then( (json) => {
component.setState({
data: json
})
console.log('parsed json', json)
})
.catch( (ex) => {
console.log('parsing failed', ex)
})
console.log(this.state.data)

setState is undefined, because you use classic function syntax instead arrow function. Arrow function takes 'this' keyword from 'parent' function, a classic function() {} creates it's own 'this' keyword.
Try this
.then(response => {
let myData = response.json()
// return myData;
this.setState({
data: myData
})
})

You're on the right track with this.setState however this is no longer in the context of the component when you call it within the function handling the response. Using a => function maintains the context of this.
fetch(URL)
.then((res) => res.json())
.then((json) => this.setState({data: json}));

REACT NATIVE
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
} from 'react-native';
export default class SampleApp extends Component {
constructor(props) {
super(props);
this.state = {
data: 'Request '
}
}
componentDidMount = () => {
fetch('http://localhost/replymsg.json', {
mode: "no-cors",
method: "GET",
headers: {
"Accept": "application/json"
},} )
.then(response => {
if (response.ok) {
response.json().then(json => {
console.warn( JSON.stringify(json.msg ));
this.setState({
data: JSON.stringify(json)
})
});
}
});
}
render() {
return (
<Text> {this.state.data}</Text>
);
}
}
AppRegistry.registerComponent('SampleApp', () => SampleApp);
JSON FILE
create a file replymsg.json and put below content and it should host to local host like : http://localhost/replymsg.json
{"status":"200ok","CurrentID":28,"msg":"msg successfully reply"}

Change the way you access the response data by using '=>' instead of function to be in the same context.
componentDidMount = () => {
let URL = 'https://jsonplaceholder.typicode.com/users'
fetch(URL)
.then(function(response) {
let myData = response.json()
return myData;
})
.then((json) => {
console.log('parsed json', json)
})
.catch(function(ex) {
console.log('parsing failed', ex)
})
} // end componentDidMount

You need to bind current context to the target function
fetch(URL)
.then(function(response) {
return response.json();
})
.then(function(json) {
this.setState({data: json})
}.bind(this))
.catch(function(ex) {
console.log('parsing failed', ex)
})

Related

How to wait for action to complete before accessing the Vue Store state?

I have Vuejs/Nuxtjs application within which I need to access a Vuex store state after it has been modified by Vuex action. Currently when I try to run the action and assignment then I get the old state and not the one which was updated after action.
How to make the code wait for action completion then run the next statement? Following is the code I have currently:
Vuejs Component:
<template>
<div>
<input v-model="formData.value" type="text">
<button #click="performAction">
Click Me
</button>
</div>
</template>
<script>
export default {
data () {
return {
formData: {
value: '',
returnValue: ''
}
}
},
methods: {
performAction () {
// Set the value within the Vuex Store
this.$store.commit('modules/DataStore/populateData', this.formData.value)
// Perform the Action
this.$store.dispatch('modules/DataStore/getData').then(() => {
console.log("AFTER COMPLETE ACTION")
})
// Assign the update value to the variable
this.formData.returnValue = this.$store.state.modules.DataStore.data
}
}
}
</script>
<style>
</style>
Vuex Store:
export const state = () => ({
data:''
})
export const mutations = {
populateData (state, data) {
state.data = data
}
}
export const actions = {
getData ({ commit, state, dispatch }) {
const headers = { 'Content-Type': 'application/json' }
this.$axios
.post('/getUrlData', state.data, { headers })
.then((response) => {
console.log("WITHIN RESPONSE")
commit('populateData',response.data)
})
.catch((error) => {
commit('populateData', 'Unable to obtain data, Error : ' + error)
})
}
}
Following are the thing I tried and nothing is working at the moment:
I tried the .then() function.
I tried Async and await but both are not working
Any suggestions will be really appreciated. Thanks in advance.
You can create getter in vuex :
export const getters = {
getData: (state) => state.data,
};
export const actions = {
async setData ({ commit }, data) {
const headers = { 'Content-Type': 'application/json' }
await this.$axios
.post('/getUrlData', data, { headers })
.then((response) => {
console.log("WITHIN RESPONSE")
commit('populateData',response.data)
})
.catch((error) => {
commit('populateData', 'Unable to obtain data, Error : ' + error)
})
}
}
then in component you can map getters and actions, and call them :
import { mapGetters, mapActions } from 'vuex'
computed: {
...mapGetters(['getData']),
},
methods: {
...mapActions(['performAction']),
async performAction() {
await this.setData(this.formData.value)
this.formData.returnValue = this.getData
}
}
You need to return your promise in your if you want to chain it in the calling method. eg:
getData ({ commit, state, dispatch }) {
const headers = { 'Content-Type': 'application/json' }
return this.$axios // now this promise will be returned and you can chain your methods together
.post('/getUrlData', state.data, { headers })
.then((response) => {
console.log("WITHIN RESPONSE")
commit('populateData',response.data);
return response.data; //this will allow you do send the data through to the next Then() call if you want to
})
.catch((error) => {
commit('populateData', 'Unable to obtain data, Error : ' + error)
})
}
This situation is a lot easier to manage with async-await IMO. It becomes:
export const actions = {
async getData ({ commit, state, dispatch }) {
const headers = { 'Content-Type': 'application/json' }
const response = await this.$axios.post('/getUrlData', state.data, { headers });
console.log("WITHIN RESPONSE")
commit('populateData',response.data);
}
}
and
methods: {
async performAction () {
// Set the value within the Vuex Store
this.$store.commit('modules/DataStore/populateData', this.formData.value)
// Perform the Action
await this.$store.dispatch('modules/DataStore/getData');
console.log("AFTER COMPLETE ACTION");
// Assign the update value to the variable
this.formData.returnValue = this.$store.state.modules.DataStore.data
}
}

Vue store dispatch error response not being passed to UI

I'm trying to get the error response from my Vue store dispatch method, into my component, so I can tell the user if the save failed or not.
store/userDetails.js
const state = {
loading: {
user_details: false,
}
}
const getters = {
// Getters
}
const actions = {
save({commit, dispatch, rootState}, payload) {
commit('setLoading', {name: 'users', value: true});
axios(
_prepareRequest('post', api_endpoints.user.details, rootState.token, payload)
).then((response) => {
if (response.data) {
commit('setState', {name: 'user_details', value: response.data.units});
commit('setLoading', {name: 'user_details', value: false});
dispatch(
'CommonSettings/setSavingStatus',
{components: {userDetails: "done"}},
{root:true}
);
}
}).catch((error)=> {
console.log(error)
return error
}
)
}
My component method
views/Users.vue
send() {
this.$store.dispatch({
type: 'Users/save',
userDetails: this.current
}).then(response => {
console.log(response)
});
},
Above, I'm logging out the response in two places.
The response in my store/userDetails.js file is logged out fine, but it's not being passed to my send() function in my component - it comes up as undefined. Any reason why it wouldn't be passed through? Is this the correct way to do this?
This works for me. Try this solution.
store.js
actions: {
save(context, payload) {
console.log(payload);
return new Promise((resolve, reject) => {
axios(url)
.then((response) => {
resolve(response);
})
.catch((error) => {
reject(error);
});
});
},
},
My Component method
App.vue
save(){
this.$store.dispatch("save", dataSendToApi).then((response)=>{
console.log(response)
})
}
Try returning axios call in the Store Action:
// add return
return axios(
_prepareRequest('post', api_endpoints.user.details, rootState.token, payload)
)
.then() // your stuff here
.catch() // your stuff here
If that won't work, use Promise in the Store Action. Like this:
return new Promise((resolve, reject) => {
return axios() // simplify for readibility reason, do your stuff here
.then((response) => {
//... your stuff here
resolve(response) // add this line
})
.catch((error) => {
// ... your stuff here
reject(error) // add this line
})
})
you should return a promise, reference link:vue doc

Cannot read property 'then' of undefined for axios wrapper

I have a bit of a similar issue like this but I can't seem to get it right. I know I have to return a promise and I think I do, although it's still not accepted. Here is my wrapper function for axios calls:
export const callGraph = (url, token) => {
return axios.get(url, {headers: { Authorization: `Bearer ${token}` }})
}
This is the function that invokes callGraph that in turn should return a Promise:
export const getGraphProfile = () => {
if (auth.getAccount()) {
auth.getToken(loginRequest)
.then(response => {
return callGraph(graphConfig.graphMeUrl, response.accessToken)
})
.catch(error => { console.log(error) })
}
}
As you can see I explicitly request return callGraph so I can use it like this:
getGraphProfile()
.then(response => { console.log('givenName ', response.data.givenName) })
.catch(error => console.log(error))
For one reason or another I'm still missing something. Thank you for your help.
You should return the axios promise
export const getGraphProfile = () => {
if (auth.getAccount()) {
return auth.getToken(loginRequest)
.then(response => {
return callGraph(graphConfig.graphMeUrl, response.accessToken)
})
.catch(error => { console.log(error) })
}
}

Resolving "Objects are not valid as a React child (found: [object Promise])" in Functional Components

I have the following in a functional component.
const [trucks, setTrucks] = React.useState([]);
React.useEffect(() => {
Request.getTrucksForToday().then((x) => {setTrucks(x)}); // <-- this is an async function to an axios request
});
return (
...
{trucks.map((truck) =>{return<LocalShippingIcon lat={truck.latitude} lng={truck.longitude} text={truck.name}/>})}
...
);
All of the solutions I have seen for this problem suggest just putting the async function in componentDidMount() (1, 2). Is there a way to solve it without doing this?
for completeness here is the function
export function getTrucksForToday() {
return axios({
method: "GET",
url: constants.backend_url + "schedule/getTrucksForToday",
headers: request_headers
})
.then(function(response) {
console.log(response.data);
return response.data;
})
.catch(function(error) {
console.log(error);
return error;
});
}
Add [] to useEffect()
const [trucks, setTrucks] = React.useState([]);
React.useEffect(() => {
Request.getTrucksForToday().then((x) => {setTrucks(x)}); // <-- this is an async function to an axios request
},[]);
return (
...
{trucks.map((truck) =>{return<LocalShippingIcon lat={truck.latitude} lng={truck.longitude} text={truck.name}/>})}
...
);

Javascript - await fetch response

I have a React-Native app and when the component mounts, I want to fetch data by calling a method in our services class, wait for that data to be returned, then set that data in setState({}). But setState({}) is called before the data is returned.
//Component class
componentDidMount(){
this.getData();
}
async getData() {
const data = await MyService.getData();
this.setState({
blah:data //undefined
});
}
//Services Class
let MyService = {
getData:function(){
axios.get(url)
.then(response => response.data)
.then((data) => {
//do stuff
return data;//need to set this to state back in component class
})
.catch(error => {
console.log(error);
});
}
}
module.exports = MyService;
You have to return the axios.get call. Otherwise the async function will return an empty promise (promise with the undefined value).
let MyService = {
getData: function() {
return axios.get(url)
.then(response => response.data)
.then((data) => {
// do stuff
return data; // need to set this to state back in component class
})
.catch(error => {
console.log(error);
});
}
}
If you return this axios call, it's itself a promise and you're not waiting until it resolves, so there's no need to use async.

Categories