Unable to this.setState() with fetch() response
Did fetch() inside form submit event handler, but unable to set the state from the fetch() callbacks
TypeError: Cannot read property 'setState' of undefined
...
constructor(props) {
super(props);
this.state = { deviceName: '', devices: [] };
this.handleChange = this.handleChange.bind(this);
this.handleSearchDevice = this.handleSearchDevice.bind(this);
}
componentWillMount() {
this.setState({
devices: this.props.devices
});
}
componentDidMount() {
}
componentWillReceiveProps(nextProps) {
this.setState({
devices: nextProps.devices
});
}
handleChange(event) {
this.setState({deviceName: event.target.value });
}
handleSearchDevice(event) {
console.log('Searching '+this.state.deviceName)
event.preventDefault();
//Get data from API
const url = 'device/name'
const data = { deviceName:this.state.deviceName}
fetch(url, { method: 'POST',
body: JSON.stringify(data),
headers:{ 'Content-Type': 'application/json' }
}).then(res => {
res.json().then(function(data) {
console.log('API Response: '+JSON.stringify(data))
try {
this.setState({devices: data.resp, deviceName: data.deviceName})
} catch(err) {
console.log('catch ' + err.stack)
this.callback1(data)
}
});
}).catch(error => {
console.error('Error:', error)
}).then(response => {
console.log('Success:', response)
});
}
callback1(data) {
this.setState({devices: data.resp, deviceName: data.deviceName})
console.log(data)
}
render() {
...
}
componentDidUpdate(prevProps) {
}
...
I expect to set the state from callbacks inside the event handler
Error screenshot
That is because you have not bound the function callback1 to this. So in your constructor you should bind it the same way you bind your other functions.
An alternative way is to make callback1 an arrow function instead so that it does not have to be bound. That would look like this:
callback1 = () => {
this.setState({devices: data.resp, deviceName: data.deviceName})
console.log(data)
}
Related
Beginner at React (and JS)...
I am trying to update text on the screen after my API helper function has completed a call. Instead, it returns empty. I have verified with the console that the data is being received. I followed the ComponentDidMount method from other similar questions but am still having no success. My code also seems to make multiple API calls, even though my intent is to only make one; I have to wonder if the issue stems from that.
Helper function:
export function apiHelper(url, data = {}, method = 'POST') {
return fetch(url, { // Return promise
method: method,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
})
.then(res => res.json())
.then((result) => {
console.log(result);
return result;
}, (error) => {
error = error;
})
}
React Component that renders incorrectly after data is received:
class Headache extends React.Component {
constructor(props) {
super(props);
this.state = {
apiResponse: null,
};
}
componentDidMount() {
apiHelper(URLredacted,JSONredacted)
.then(response => {
this.setState({
apiResponse: response
});
console.log("state set.",response)
});
}
render() {
const jsonResponse = JSON.stringify(this.props.apiResponse)
return (
<div>
<img className="logoheader" src = {logo}/>
<ColoredLine color="grey"/>
<p>{jsonResponse}</p>
</div>
);
}
}
export function apiHelper(url, data = {}, method = 'POST') {
return fetch(url, { // Return promise
method: method,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
})
.then(res => res.json())
.catch(err=>console.log(err))
}
second page:
class Headache extends React.Component {
constructor(props) {
super(props);
this.state = {
apiResponse: null,
};
}
componentDidMount() {
apiHelper(URLredacted,JSONredacted)
.then(response => {
this.setState({
apiResponse: response
});
console.log("state set.",response)
});
}
render() {
const jsonResponse = JSON.stringify(this.props.apiResponse)
return (
<div>
<img className="logoheader" src = {logo}/>
<ColoredLine color="grey"/>
<p>{jsonResponse && ""}</p>
</div>
);
}
}
if these code dont work you will be need asyn function because you need to wait to fetch data.
Utils.js
This is my function to handle requests and I want to work the response with callback.
static Request(data, callback) {
const method = data[0] || "";
const url = data[1] || "";
const param = data[2] || "";
const headers = data[3] || "";
if (method.toLowerCase() === 'post') {
var data = {
url: url,
method: method.toLowerCase(),
contentType: 'application/json',
data: {
param
}
};
if (headers) {
data.headers = {
headers
};
}
axios(data).then(function (response) {
console.log(response);
})
.catch(function (e) {
console.log(e);
});
} else if (method.toLowerCase() === 'get') {
axios.get(url)
.then((response) => {
// console.log(response.data);
callback(response.data);
return (response.data);
})
} else {
console.log("Undefined");
}
}
This is how I try to get the response from the callback. (different file)
handleClick() {
Utils.Request(['get', 'www.URL.com'],
function Response(data) {
this.state({ response: data.msg })
})
}
Then in a button
button onClick={this.handleClick()}>Teste</button
The error I get is Unhandled Rejection (TypeError): this is undefined
What im doing wrong?
Full component here. Its a new fresh component..
export default class Discover extends Component {
constructor(props) {
super(props);
this.state = { itemArray: [], response: null };
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
Utils.Request(['get', 'url'],
function Response(data) {
this.setState({ response: data.msg })
})
}
render() {
return <React.Fragment>
<button onClick={() => { console.log(this.state.response) }}>Click</button>
<button onClick={() => this.handleClick()}>Teste</button>
</React.Fragment>;
}
}
I have the following method:
const getAgentData = () => {
axios.get('http://localhost:3000/api/agent', {
headers: {
'Content-Type': 'application/json'
}
})
.then(async res => {
const dbData = res.data.data;
let dataForTable = dbData.map( el => {
let obj = {};
obj._id = el._id;
obj.name = el.name;
obj.phone = el.phone;
if(el.name) {obj.email = el.email}
return obj;
})
await dispatch({ type: 'ADD_PHOTOGRAPHERS', dataForTable });
})
.then(() => {
setLoading(false)
})
.catch((error) => {
console.error(error)
})
}
I update the redux state with this line: await dispatch({ type: 'ADD_PHOTOGRAPHERS', dataForTable });
in a child component I run this:
componentDidMount() {
console.log(this.props.photographers)
}
In the original state this.props.photographers = [];
this is what is logged to the console, it never logs the udpated redux state. From my understading redux should automatically update and it should console.log the udpated state. Any idea what I'm doing wrong?
I also tried logging data with the props being here but it's also an empty array:
class DataTableComponent extends Component {
constructor(props) {
super(props)
this.state = {
data: this.props.photographers,
loading: false,
name: '',
phone: '',
email: '',
}
}
...
My redux map to props in the child component I'm describing is:
function mapStateToProps(state) {
return {
photographers: state.Customizer.photographers
}
}
export default connect(mapStateToProps)(DataTableComponent);
Check if await dispatch({ type: 'ADD_PHOTOGRAPHERS', dataForTable });, seems that you are not sending the payload correctly.
You can log or debug how this action payload data is coming to its reducer.
Now i am trying to fatching data from API using axios and React JS. But when i use this code i got this error:
TypeError: this.state.countries.map is not a function
I have state data:[] and I am trying to set the values of URL in the state. So my code like this:
//get user token
const GetTokens = 'Bearer '.concat(localStorage.getItem('token'));
export default class Index extends Component {
constructor(props) {
super(props);
this.state = {
error: null,
countries: [],
response: {}
}
}
componentDidMount() {
axios.get(apiUrl + '/country-lists', { headers: { Authorization: GetTokens } })
.then(response => response.data).then(
(result) => {
this.setState({
countries: result
});
},
(error) => {
this.setState({ error });
}
)
}
And in my render like this:
{this.state.countries.map(list => (
{list.name}
))}
Also i tried like this.
render() {
const (countries ) = this.state
const (countries = []) = this.state
In my opinion, I made no mistake while getting a token and referencing the map. But I can't figure out where I made the mistake.
By looking at your console.log I think you should use result.data
componentDidMount() {
axios.get(apiUrl + '/country-lists', { headers: { Authorization: GetTokens } })
.then(response => response.data).then(
(result) => {
this.setState({
countries: result.data
});
},
(error) => {
this.setState({ error });
}
)
I am decoding a token to get the current users email address and setting to facultyEmail state and sending that to the backend to get a response. But facultyEmail is empty because componentDidMount is asynchronous ,it works outside the componentDidMount() but I don't know any way to handle the axios get request with params outside the componentDidMount i dont have event to invoke it.Thanks for the help
componentDidMount() {
const token = localStorage.usertoken;
const decoded = jwt_decode(token);
this.setState({
facultyEmail: decoded.email
});
axios
.get("faculty/Course", {
params: {
facultyEmail: this.state.facultyEmail
}
})
.then(res => {
this.setState({
class: res.data
});
})
.catch(err => {
console.log(err);
});
console.log("courses", this.state.facultyEmail);
}
The setState is asynchronous. You have to use setState callback or async/await
using callback
componentDidMount() {
const token = localStorage.usertoken;
const decoded = jwt_decode(token);
this.setState({
facultyEmail: decoded.email
}, () => {
axios
.get("faculty/Course", {
params: {
facultyEmail: this.state.facultyEmail
}
})
.then(res => {
this.setState({
class: res.data
});
})
.catch(err => {
console.log(err);
});
console.log("courses", this.state.facultyEmail);
});
}
using async/await
async componentDidMount() {
try {
const token = localStorage.usertoken;
const decoded = jwt_decode(token);
await this.setState({
facultyEmail: decoded.email
});
const res = await axios.get("faculty/Course", {
params: {
facultyEmail: this.state.facultyEmail
}
})
this.setState({
class: res.data
});
console.log("courses", this.state.facultyEmail);
} catch (err) {
console.log(err);
}
}
You are using same email you are using in setState to make the API call, there is no need for two setStates. That would cause us anomalies and is not a recommended practice. You can do this in two ways:
Way 1:
componentDidMount() {
const token = localStorage.usertoken;
const decoded = jwt_decode(token);
axios.get("faculty/Course", {
params: {
facultyEmail: decoded.email
}
}).then(res => {
this.setState({
class: res.data,
facultyEmail: decoded.email
});
}).catch(err => {
console.log(err);
});
}
render() {
console.log(this.state.class, this.state.facultyEmail);
// This will have the values from setstate triggered inside axios.
return(
<div> Sample </div>
)
}
Alternate approach:
loadDataFromApi(email) {
axios.get("faculty/Course", {
params: {
facultyEmail: email
}
}).then(res => {
this.setState({
class: res.data
});
}).catch(err => {
console.log(err);
});
}
componentDidMount() {
const token = localStorage.usertoken;
const decoded = jwt_decode(token);
this.setStats({
facultyEmail: decoded.email
}, () => {
// The callback function would reflect the updated email.
this.loadDataFromApi(this.state.facultyEmail);
});
}
Why not just store facultyEmail in memory until the 2nd setState, avoiding the first one? The axios call is async, so you'll need to put the console.log in the render function (and you should only log it once it's actually in state).
componentDidMount() {
const token = localStorage.usertoken;
const decoded = jwt_decode(token);
const facultyEmail = decoded.email;
axios
.get("faculty/Course", { params: { facultyEmail } })
.then(res => { this.setState({ class: res.data, facultyEmail }); })
.catch(err => { console.log(err); });
}
render() {
if (this.state.facultyEmail) console.log("courses", this.state.facultyEmail);
return ();
}