Replacing componentWillMount() with constructor does not work - reactjs

A class is used to intercept axios error. to bind arrow functions, componentDidMount() is being used. now I need to initiliaze data from the server so I have to use componentWillMount() except it will be removed in React 17 and warning message suggest I use constructor. When I do it gives me an error.
import React, {Component} from "react";
import Modal from "../../components/UI/Modal/Modal";
import Aux from "../Auxiliary/Auxiliary";
const withErrorHandler = (WrappedComponent, axios) => {
return class extends Component{
state = {
error: null
};
// componentWillMount() {
// axios.interceptors.request.use(request => {
// this.setState({
// error: null
// });
// return request;
// });
// axios.interceptors.response.use(res => res, (error) => {
// this.setState({
// error: error
// })
// });
// }
constructor(props) {
super(props);
axios.interceptors.request.use(request => {
this.setState({
error: null
});
return request;
});
axios.interceptors.response.use(res => res, (error) => {
this.setState({
error: error
})
});
}
errorConfirmedHandler = () => {
this.setState({error: null})
};
render() {
return (
<Aux>
<Modal show={this.state.error} modalClosed = {this.errorConfirmedHandler}>
{this.state.error ? this.state.error.message : null}
</Modal>
<WrappedComponent {...this.props}></WrappedComponent>
</Aux>
);
}
}
};
export default withErrorHandler;
I removed .json from URL to produce an error
class BurgerBuilder extends Component {
state = {
ingredients: null,
totalPrice: 4,
purchasable: false,
purchasing: false,
loading: false,
// axiosError: null
};
componentDidMount() {
axios.get('https://burger-m.firebaseio.com/ingredients').then(response => {
this.setState({ingredients: response.data});
}).catch(error => {});
}
..
export default withErrorHandler(BurgerBuilder, axios);
&
Error Message: "index.js:1 Warning: Can't call setState on a component that
is not yet mounted. This is a no-op, but it might indicate a bug in your
application. Instead, assign to `this.state` directly or define a `state = {};`
class property with the desired state in the _temp component."
componentWillMount() does work however. so What Should I change?

Keep constructor simple by just adding state and do not register axios interceptors in constructor method, instead register interceptors in render method.
componentWillUnmount(){
console.log('unregistering interceptors', this.reqInterceptor, this.resInterceptor)
axios.interceptors.request.eject(this.reqInterceptor);
axios.interceptors.response.eject(this.resInterceptor);
}
render() {
if(!this.resInterceptor){
console.log('Registering Interceptors');
this.reqInterceptor = axios.interceptors.request.use(req => {
this.setState({ error: null })
return req;
})
this.resInterceptor = axios.interceptors.response.use(response => response, error => {
this.setState({error})
})
}
return (
<Aux>
<Modal show={this.state.error} modalClosed={this.errorConfirmedHandler }>{this.state.error ? this.state.error.message : null}</Modal>
<WrappedComponent />
</Aux>
)

The constructor initializes the state, that's why you are prohibited from using setState() there.
You could use componentDidMount() instead, I think it matches better your needs and will avoid any confusion.
const withErrorHandler = (WrappedComponent, axios) => {
return class extends Component{
state = {
error: null
};
componentDidMount() {
axios.interceptors.request.use(request => {
this.setState({
error: null
});
return request;
});
axios.interceptors.response.use(res => res, (error) => {
this.setState({
error: error
})
});
}
errorConfirmedHandler = () => {
this.setState({error: null})
};
render() {
return (
<Aux>
<Modal show={this.state.error} modalClosed = {this.errorConfirmedHandler}>
{this.state.error ? this.state.error.message : null}
</Modal>
<WrappedComponent {...this.props}></WrappedComponent>
</Aux>
);
}
}
};

Use this.state = {error: null} and this.state = {error} instead of setState in then blocks of interceptors.

Regarding this specific example I think the best solution is to use a "temporary" variable , call the use method on the interceptors, and the define the state. All this inside the constructor.
Like so:
constructor() {
super();
let tempErrorState = null;
this.requestInterceptor = axios.interceptors.request.use(req => {
tempErrorState = null;
return req;
});
this.resoponseInterceptor = axios.interceptors.response.use(res => res, error => {
tempErrorState = error;
});
this.state = {
error: tempErrorState
};
}

As per your condition, you can't use the componentDidMount() method. You MUST NOT use the render() method for setting up Axios interceptors because you are using withErrorHandler as a higher-order component which can lead to many interceptors eventually created while wrapping other components too. This is because for each render cycle, you are unnecessarily setting up an interceptor if you define it inside a render() method. You should set up an Axios interceptor only once in the component.
The constructor() would be the best place to set this up (as per the latest version of React where componentWillMount() is deprecated).
You can edit your code to include a constructor():
constructor(props) {
super(props);
this.state = {
error: null
};
// clear the error when sending a request
axios.interceptors.request.use(req => {
this.state = {
error: null
};
return req;
});
axios.interceptors.response.use(res => res, err => {
this.state = {
error: err
};
});
}
Notice that here, you are not using the setState() method which throws a warning in constructor but you are directly setting the state, which is allowed only inside the constructor().

Related

How do I change a state after getting data from API?

constructor(props) {
super(props);
this.state = {
message: ""
};
}
async getData() {
this.setState({...this.state})
await axios.get("https://g...")
.then(function(response) {
console.log(response);
this.setState({message: response.data})
}).bind(this)
}
render() {
return (
<div>
{this.state.message}
</div>
);
}
I tried to use this code to get data from the API. However, the message that is printed out is only linked to the original constructor, and the getData() function does not change the state. How should I go around changing the state after getting data?
You should use componentDidMount, and put the function requesting data in componentDidMount life circle.
By the way, you can add a loading to enhance the user experience : )
import React from 'react';
import "./styles.css";
const BASE_URL = 'https://api.github.com';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
message: ''
}
}
componentDidMount() {
this.getData();
}
async getData() {
try {
const result = await fetch(`${BASE_URL}/repos/facebook/react`);
const toJson = await result.json();
const stringify = JSON.stringify(toJson, null, 2);
this.setState({
message: stringify
})
} catch (error) {
// ignore error.
}
}
render() {
const { message } = this.state;
return (
<div>
{message}
</div>
)
}
}
export default App;
If you are using 'async' and 'await' you don;t have to use then() function
you can write
const data = await axios.get("url")
console.log(data.data)
this.setState({message:data.data})

Implementing promise in react

I am trying to implement simple promise is react.
below is my code:
import React, { Component } from "react";
class App extends Component {
constructor(props) {
super(props);
this.state = {
name: '',
};
}
componentWillMount() {
var promise = new Promise( (resolve, reject) => {
let name = 'Shubham'
if (name === 'Shubham') {
resolve("Promise resolved successfully");
}
else {
reject(Error("Promise rejected"));
}
});
promise.then( result => {
this.setState({name: result});
}, function(error) {
this.setState({name: error});
});
}
render() {
return (
<div className="App">
<h1>Hello World</h1>
<h2>{this.state.name}</h2>
</div>
);
}
}
export default App;
This works as long as the name matches. If I change let name = 'Shubham' to let name = 'Shaurya' then it is giving me error Unhandled Rejection (TypeError): Cannot read property 'setState' of undefined. I am not able to understand what error am I doing.
In your promise error handler, this refers to function instance and not to the class component's. Change it to an arrow function:
promise.then( result => {
...
}, (error) => {
this.setState({name: error});
})

How to set the state of a component with the information obtained from a promise to avoid memory leaks in react

Where should I set the state of the component with the information gotten from a resolved promise in a way that if the component is unmounted before the last two actions are done it doesn't cause a memory leak?
class Schedule extends Component {
constructor(props) {
super(props);
this.state = {
someKey: ''
};
}
someMethod = () => {
axios.get('some-url')
.then((response) => {
setState({someKey: response})
})
}
render(){
return(
<button onClick={this.someMethod}>Click Me</button>
)
}
}
export Schedule
If I leave the page before the new state is set if gives me this error:
'Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.'
How do I handle this?
In general to handle API calls I would recommend you some app state management F.E Redux / MobX. But for simple usecase you can try go with:
class App extends Component {
constructor(props) {
super(props);
this.isMounted = false;
this.state = {
someKey: []
};
}
componentDidMount() {
this.isMounted = true;
}
someMethod = () => {
axios.get("some_url").then(res => {
if (this.isMounted) {
this.setState({
someKey: res
});
}
});
};
componentWillUnmount() {
this.isMounted = false;
}
render() {
...
}
}
Note that you can also cancel the axios request.
class App extends Component {
constructor(props) {
super(props);
this.source = axios.CancelToken.source();
this.state = {
someKey: []
};
}
someMethod = () => {
axios.get("some_url"{ cancelToken: this.source.token }).then(res => {
this.setState({
someKey: res
});
})
// catch axios cancel error, we don't want to show it
.catch(err => {
// if it's not an axios cancel error, we may want to re-throw the error (depending on the app structure)
if(!err.name === 'Cancel') {
throw err;
}
});
};
componentWillUnmount() {
this.source.cancel('Cancel message');
}
render() {
...
}
}
https://github.com/axios/axios#cancellation

Component not updating when state changes

This is the first time I'm using React without JSX so I may have made an obvious error, but I can't seem to figure out why my component is not updated.
I make a xhr request in componentDidMount and attempt to set this.state.isLoading to false. This should cause the loading message to be removed once the xhr request has finished.
Here's my code:
import React from 'react'
import request from 'superagent'
export class SurveyList extends React.Component {
constructor () {
super()
this.state = {
error: false,
surveys: [],
isLoading: true
}
}
componentDidMount () {
request
.post('/surveys')
.set('accept', 'json')
.end((err, res) => {
if (err) this.state.error = err
this.state.isLoading = false
})
}
render () {
const items = this.state.surveys.map(survey => React.createElement('li', survey))
const loading = React.createElement('p', null, 'Loading ...')
const list = React.createElement('ul', null, items)
return this.state.isLoading ? loading : list
}
}
I render like so:
const surveyListElem = document.getElementById('survey-list')
if (surveyListElem) {
ReactDOM.render(
React.createElement(SurveyList),
surveyListElem
)
}
Setting the state directly will have no effect. You need to use setState() instead to trigger a re-render.

Object property returning undefined after mounting component and setState

I'm using a callback from axios.get to set the state of a React component. The data property of the response contains an array of objects which I use to set state.
When I log the state property to the console there are no issues and I can see the array of objects. However if I try to log one of those objects individually I get the error:
Cannot read property '0' of null at ResponsiveTable.render
Below is the code for my component:
class ResponsiveTable extends React.Component {
constructor (props) {
super(props)
this.state = {
returnedQuery: null
};
}
componentDidMount() {
axios.get('/api/latestLeads')
.then((response) => {
this.setState({
returnedQuery: response.data
});
})
.catch(function (error) {
console.log(error);
});
}
render() {
console.log(this.state.returnedQuery[0]);
return (
<div>
<h1>test</h1>
</div>
);
}
}
Sure at first render your returnedQuery is null so you are getting this error.
If you want to use this.state.returnedQuery[0] check if it's exist and it's length > 0:
render() {
if (this.state.returnedQuery && this.state.returnedQuery.lenth > 0){
return (
<div>
{this.state.returnedQuery.map(...)}
</div>
);
} else {
return <div>loading data...</div>
}
}
You could try this:
class ResponsiveTable extends React.Component {
constructor (props) {
super(props)
this.state = {
returnedQuery: null
};
this.getData();
}
getData = () => {
axios.get('/api/latestLeads')
.then((response) => {
this.setState({
returnedQuery: response.data
}, () => {console.log(this.state.returnedQuery);}); //What does this console.log() say?
})
.catch(function (error) {
console.log(error);
});
}
render() {
console.log(this.state.returnedQuery[0]);
return (
<div>
<h1>test</h1>
</div>
);
}
}
I found the solution which was quite similar to Andrew's above, I used the component's internal state to determine whether the axios.get method had returned. Below is the working code, I'm now able to access elements within the returned array and their properties.
class ResponsiveTable extends React.Component {
constructor (props) {
super(props)
this.state = {
returnedQuery: null
};
}
componentDidMount() {
const self = this;
// let returnedQuery;
axios.get('/api/latestLeads')
.then((response) => {
self.setState({
returnedQuery: response.data
});
console.log(response.data);
})
.catch(function (error) {
console.log(error);
});
}
render() {
return (
<div>
<h1>{'This will always render'}</h1>
{ this.state && this.state.returnedQuery &&
<div>{this.state.returnedQuery[0].email}</div>
}
</div>
)
}
}

Resources