Handling multiple axios get requests inside componentDidMount - javascript

I have a React component like this:
class Example extends Component {
constructor(props) {
super(props);
this.state = {
name: '',
address: '',
phone: ''
}
}
componentDidMount() {
//APIcall1 to get name and set the state
//i.e., axios.get().then(this.setState())
//APIcall2 to get address and set the state
//APIcall3 to get phone and set the state
}
}`
As you can see I am making three API get requests to get the details and setting the state three times after getting the data. Due to this, I am getting this error:
Warning: Cannot update during an existing state transition (such as within render or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to componentWillMount.
By the way, I am not causing a state change in the render method. Anyway to solve this issue?

As axios.get returns a promise, you can link them together before calling setState. For example using Promise.all:
componentDidMount() {
Promise.all([
APIcall1, // i.e. axios.get(something)
APIcall2,
APIcall3
]).then(([result1, result2, result3]) => {
// call setState here
})
}
Just beware that if any of the api calls fail, Promise.all will catch, and setState won't be called.

In axios you have the method axios.all:
function getUserAccount() {
return axios.get('/user/12345');
}
function getUserPermissions() {
return axios.get('/user/12345/permissions');
}
axios.all([getUserAccount(), getUserPermissions()])
.then(axios.spread(function (acct, perms) {
// Both requests are now complete
}));
Or you can use the standard Promise.all:
function getUserAccount() {
return axios.get('/user/12345');
}
function getUserPermissions() {
return axios.get('/user/12345/permissions');
}
Promise.all([getUserAccount(), getUserPermissions()])
.then(data => {
// Both requests are now complete
});

Related

React Class Runs Code Excessively and Does Not Update State

In the constructor, I'm running this.onLoadAccountRetrieve(); to use the API to get the account details, and if an account is returned the use setState() to update the account and isLoginActive variables.
// outside of class
export interface PageState {
account: AccountDTO;
anchorEl: HTMLElement | null;
isLoginActive: boolean;
isMenuOpen: boolean;
}
// inside class
constructor(props: PageProps) {
super(props);
this.state = {account: null,anchorEl: null,isLoginActive: false,isMenuOpen: false};
this.onLoadAccountRetrieve();
console.log('constructor state', this.state);
}
componentDidMount(): void {
// if user not found, and login state updated, redirect to login page
this.redirectIfUserNotFound();
}
private redirectIfUserNotFound() {
if (this.state.isLoginActive === false) {
window.location.assign('/login');
}
}
private async onLoadAccountRetrieve() {
const res: AxiosResponse = await axios()
.then((res) => {
// original version - attempt 1
this.setState({ account: res.data, isLoginActive: true });
})
// updated version - attempt 2 - code was moved here
return this.setState({ account: res.data, isLoginActive: true });
}
What I'm seeing is the onLoadAccountRetrieve method being run 4 times that fail, and another 12 times that return the account data in a console.log but the state variables don't get updated.
Questions:
How do I get react to not run the code 16 times?
How do I get my data to run before render()?
If I can get these two to be fixed, I'm expecting that the setState() (which isn't updating anything now) would self resolve itself.
Firefox Dev Browser Console:

react-query within a class component where we have send/receive functions

I want to use React Query with a specific code structure, and I'd like to know if this is possible for me.
I have a React class component where sending the request and receiving the response are done in separate places
example:
class Engine{
sendMessage(from, message){
...
// for each subscriber
subscriber.receive(from, message)
}
listenFor(subscriber, chanel){
...
}
}
class ComponentA extends React.Component{
messageId;
constructor(props){
...
Engine.listenFor(this, 'APIResponse');
this.state = { data: {} }
}
componentDidMount(){
fetchData();
}
fetchData(){
let msg = createAPIRequestMsg({...});
this.messageId = msg.id;
Engine.send(msg);
}
recieve(from, message){
if(message.id == this.messageId){
let respones = message.response;
this.setState({ data: response });
}
}
}
Now I want someway to use react-query within this structure, I know we can use it within a class in different ways, but my question is how to use it when the function that is supposed to return a response is not returning the response, for example fetchData will not return the response, it will just send a message, and the receive function is the one responsible to get the response back, so in this structure we can't use the fetchData with useQuery, is there a workaround that?

Reactjs - Why can't i set state?

Hi i'm trying to fetch a user data from jsonplaceholder and update my state with that data. I had no problem fetching the data and logging it to the console. But when i try to setState, i still get an empty object.
I appreciate any help. Thanks.
This is my code:
class ProfilePage extends React.Component {
state = {
profileDetails: {},
};
componentDidMount() {
this.fetchDetails();
}
fetchDetails = async () => {
const baseUrl = "https://jsonplaceholder.typicode.com";
const pathname = this.props.history.location.pathname;
const response = await fetch(`${baseUrl}${pathname}`);
const data = await response.json();
console.log(data); // I can see the data i want here in the console.
this.setState = { profileDetails: data };
console.log(this.state.profileDetails); // I get an empty object here.
};
render() {
return <h1>Name: {this.state.profileDetails.name}</h1>;
}
}
export default ProfilePage;
Thanks everyone for taking the time to answer. Apparently i used setState wrong and missed the fact that it's asynchronous.
From docs of setState
React does not guarantee that the state changes are applied
immediately.
If you want to use up-to-date data, use callback argument (and use it as function, instead of assignment, because it is a method, not a property)
this.setState({ profileDetails: data }, () => {
console.log(this.state.profileDetails)
})
Change this
this.setState = { profileDetails: data };
console.log(this.state.profileDetails);
into this
this.setState({ profileDetails: data });
Put console.log(this.state.profileDetails); inside render for you to see your new state.
setState is a function that recieves data as parameters.
but you use it like setState is a json object
setState - is a method.
Please change code like this - this.setState({ profileDetails: data });
The right way to set state is this,
this.setState({ profileDetails: data })
You have to set state by this way only.
Give a condition for check the data is available or not:-
if(data)
this.setState = ({ profileDetails: data });

The most robust way to setState in React JS

I have a firebase realtime database, a JSON based no sql database, I have 5 fields in that database, storing these:
field 1: STRING
field 2: STRING
field 3: STRING
field 4: STRING
field 5: ARRAY OF OBJECTS
so that database with real example looks something like this:
name: STRING
age: NUMBER
email: STRING
phoneNumber: STRING
skills: OBJECT[]
now I am downloading all these info from the firebase as a single variable(var) called
snapshot.val(). and then updating all the states using setState at once like this:
this.setState({
state_name: snapshot.val().name,
state_age: snapshot.val().age,
state_email: snapshot.val().email,
state_phoneNumber: snapshot.val().phoneNumber,
state_skills: snapshot.val().skills
});
now this statement works fine if all the values are there in the database and it also works fine if the strings aren't there in the database but it runs into an error if the skills field is undefined in the database.
So setState doesn't throw an error when strings are undefined but it does throw error when arrays are undefined. it happened even with simple string array. Now if one field doesn't exist and an error is thrown, the entire setState fails, so in summary, if multiples states are being updated at once and one of the state upadate fails with an exception or error, all the updates fail and I have been updating such delicate data with separate setState statements with only one state being updated inside its own try catch block. So to update multiple states that have the potential to crash, I am using multiple try catch blocks. something like this:
try {
this.setState({
ownerNameValue: snapshot.val().owner_name,
});
} catch {}
try {
this.setState({
phoneNumberValue: snapshot.val().phone_number,
});
} catch {}
try {
this.setState({
specialityValue: snapshot.val().specialty,
});
} catch {}
try {
if (snapshot.val().services != undefined) {
this.setState({
selectedOptionSerivce: snapshot.val().services,
});
}
} catch {}
try {
if (snapshot.val().home_service != undefined) {
this.setState({
selectedOption: snapshot.val().home_service,
});
}
} catch {}
Now is there a more robust and efficient way to achieve this?
Some way to update all the states at once and not crash if one of the state has its value as undefined or simply fails. So that way, the states that run into no problems, get updated, but the states that run into exceptions and errors, simply get discarded without showing any error.
so it will be something like this:
field 1: STRING ->> state updated
field 2: STRING ->> state updated
field 3: STRING ->> state updated
field 4: STRING ->> state updated
field 5: undefined ->> state update FAILED (an array of objects was excepted)
NOTICE: this question is not limited to firebase, its about setState handling exceptions and errors of all kind.
You can assign to skills an empty array when it is undefined like this:
this.setState({
state_name: snapshot.val().name,
state_age: snapshot.val().age,
state_email: snapshot.val().email,
state_phoneNumber: snapshot.val().phoneNumber,
state_skills: snapshot.val().skills || [],
});
There is absolutely no sense in doing something like:
try {
if (snapshot.val().home_service != undefined) {
this.setState({
selectedOption: snapshot.val().home_service
});
}
} catch{ }
Calling setState won't crash if you set undefined as a value. The only case you may face an error is when you accidentally forgot to check this value inside of a lifecycle methods or render:
render() {
return this.state.skills.map(...)
}
Here, if skills is undefined you'll get an error. For efficiency sake you may do something like:
class Some {
constructor() {
this.state = {...}
}
get skills() {
return this.state.skills || []
}
render() {
return this.skills.map(...)
}
}
First, you don't need to wrap this.setState inside of a try/catch. setState will not throw an error if you try to set some property of your state to undefined - this is definitely allowed.
class App extends React.Component {
constructor(props) {
super(props);
this.state = { foo: "bar" };
}
componentDidMount() {
setTimeout(() => {
this.setState({
foo: undefined
});
}, 4000);
}
render() {
return this.state.foo !== undefined ? (
<h1>this.state.foo is not undefined</h1>
) : (
<h1>this.state.foo IS undefined</h1>
);
}
}
ReactDOM.render(
<App />,
document.getElementById("app")
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>
So, when you say that it "fails" because you set a piece of state to undefined, you're most likely not handling this case elsewhere in your code. This is common when you're expecting something to be an array and you're trying to use the map method, but it turns out to be undefined.
render() {
// this.state.skills is `undefined
// and you're trying to use the `map` method
// which will fail
return (
<div>
{this.state.skills.map(...)}
</div>
)
}
What you should do is to specify fallbacks for items that come back as undefined:
class App extends React.Component {
updateState = () => {
firebase()
.then(snapshot => {
this.setState({
state_name: snapshot.val().name || '',
state_age: snapshot.val().age || 0,
state_email: snapshot.val().email || '',
state_phoneNumber: snapshot.val().phoneNumber || '',
state_skills: snapshot.val().skills || []
})
})
.catch(/* handle errors appropriately */)
}
}

React - getDerivedStateFromProps and axios

I would like to set state variable books as result of API. I cannot do it in componentDidMount, because I don't have token at the beginning and need it to get result from API. Nothing happens with state books when I run following code. If I put state.books=res.data before return I got a result, but after manually refresh page.
constructor(props) {
super(props);
this.state = {
books: [],
};
}
and
static getDerivedStateFromProps(nextProps, state) {
if (nextProps.token){
axios.defaults.headers = {
"Content-Type": "application/json",
Authorization: "Token " + nextProps.token
}
axios.get('http://127.0.0.1:8000/api/book/own/')
.then(res => {
return {
books: res.data,
}
})
}
data from the API looks like:
{
id: 66,
isbn: "9780545010221",
title: "Title",
author: "Author,Author",
}
In the render method I call component with this.static.books data.
Could you please advise me?
This is a very common pitfall: you are returning something inside the promise handler (then), thinking that that would return from the function that created the promise (getDerivedStateFromProps). That's not the case.
I'm afraid you can't use getDerivedStateFromProps for asynchronous code like this. However, you don't have to, given that react is, uhm, reactive.
componentDidUpdate() {
if (this.props.token) {
axios.get('http://127.0.0.1:8000/api/book/own/')
.then(res => this.setState({books: res.data})) ;
}
}

Categories