setState my response data in React.js (axios) - reactjs

I don't know, why I can't setState my response data of REST API. Console.log just working. Where is problem? I can't find problem in my code.
class RecentTransactions extends Component {
constructor() {
super();
this.setState = {
recentTransactionsSender: [],
recentTransactionsRecipient: [],
};
}
componentDidMount() {
axios
.all([
axios.get('http://localhost:3000/api/transactions/recipient/1'),
axios.get('http://localhost:3000/api/transactions/sender/1'),
])
.then(
axios.spread(
(recentTransactionsRecipient, recentTransactionsSender) => {
this.setState({
recentTransactionsRecipient: recentTransactionsRecipient.data,
recentTransactionsSender: recentTransactionsSender.data,
});
},
),
)
.catch(err => {});
}

First and foremost you are setting the initial state incorrectly. You should be assigning the initial state to the state object rather than to setState.
this.state = {
recentTransactionsSender: [],
recentTransactionsRecipient: [],
};
Moreover, since you aren't doing anything in the constructor other than setting the initial state, you can remove it and instead do
class RecentTransactions extends Component {
state = {
recentTransactionsSender: [],
recentTransactionsRecipient: [],
};
}
Other than that, without having more details on what's not working it's impossible to help you any further.

Related

Why are two network calls being made, when fetch in setState?

When I use fetch in setState the function makes two network requests, but I expect one request.
Why is this happening and how to prevent it?
import React from 'react';
class TestFetch extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(() => {
fetch('http://example.com/', {
mode: 'no-cors'
})
.then(data => {
console.log(data)
});
});
}
render() {
return (
<button onClick={this.handleClick}> Test </button>
)
}
}
export default TestFetch
Another version with setState in the fetch. Now I have one network call, but two values in my state after one click:
import React from 'react';
class TestFetch extends React.Component {
constructor(props) {
super(props);
this.state = {
'newItems': []
};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
fetch('http://example.com/', {
mode: 'no-cors'
})
.then(data => {
this.setState((state) => {
state.newItems.push("value")
})
console.log(this.state)
});
}
render() {
return (
<button onClick={this.handleClick}> Test </button>
)
}
}
export default TestFetch
Ok, basically it has this effect in this example as well:
import React from 'react';
class TestFetch extends React.Component {
constructor(props) {
super(props);
this.state = {
'newItems': []
};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(state => {
state.newItems.push("value")
})
console.log(this.state);
}
render() {
return (
<button onClick={this.handleClick}> Test </button>
)
}
}
export default TestFetch
Don't do api call in setState.. take state variable and store api response data in it and use state variable when ever it's required.
import React from 'react';
class TestFetch extends React.Component {
constructor(props) {
super(props);
this.state = {appData: null};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
fetch('http://example.com/', {
mode: 'no-cors'
})
.then(data => {
console.log(data)
this.setState(() => {appData: data});
});
}
render() {
return (
<button onClick={this.handleClick}> Test </button>
)
}
}
export default TestFetch
Why is this happening...
My guess would be you are rendering your app into a React.StrictMode component. See Detecting unintentional side-effects
Strict mode can’t automatically detect side effects for you, but it
can help you spot them by making them a little more deterministic.
This is done by intentionally double-invoking the following functions:
Class component constructor, render, and shouldComponentUpdate methods
Class component static getDerivedStateFromProps method
Function component bodies
State updater functions (the first argument to setState)
Functions passed to useState, useMemo, or useReducer
In other words, the setState is called twice by React to help you find unintentional side-effects, like the double fetching.
...and how to prevent it?
Just don't do side-effects in the setState callback function. You likely meant to do the fetch and in the Promise chain update state.
handleClick() {
fetch('http://example.com/', {
mode: 'no-cors'
})
.then(data => {
console.log(data);
this.setState( ......); // <-- update state from response data
});
}
Update
Another version with setState in the fetch. Now I have one network
call, but two values in my state after one click:
In your updated code you are mutating the state object. Array.prototype.push updates the array by adding the new element to the end of the array and returns the new length of the array.
Array.prototype.push
this.setState(state => {
state.newItems.push("value") // <-- mutates the state object
})
I believe you see 2 new items added for the same reason as above. When updating arrays in state you need to return a new array reference.
You can use Array.prototype.concat to add the new value and return a new array:
this.setState(prevState => {
newItems: prevState.newItems.concat("value"),
});
Another common pattern is to shallow copy the previous state array into a new array and append the new value:
this.setState(prevState => {
newItems: [...prevState.newItems, "value"],
});
Additionally, once you sort out your state updates, the console log of the state won't work because React state updates are asynchronously processed. Log the updated state from the componentDidUpdate lifecycle method.
componentDidUpdate(prevProps, prevState) {
if (prevState !== this.state) {
console.log(this.state);
}
}

State is not getting updated in componentWillMount

class ViewExchange extends React.Component{
state={list:[],refresh:false}
componentWillMount(props){
if(_.isEmpty(Cookies.get())){
this.props.history.push("/signup")
}
else{
console.log("is present")
let platform = Cookies.get('platform')
console.log(platform)
axios.post('http://localhost:3001/user/viewexchange',{platform})
.then(res=>{
console.log(res.data)
this.setState({list:res.data})})
console.log(this.state.list)
}
}
render(){
return (
<div>
<button onClick={()=>this.setState({refresh:true})}>refresh</button>
{console.log(this.state.refresh)}
</div>
);
}
}
export default withRouter(ViewExchange);
setState state update operations are async, so it will take a bit of time to update the state. But your log executes before it updates. Instead you can make use of passing function as a second param in the setState:
this.setState({list:res.data}, () => console.log(this.state.list))
another way is:
this.setState(state => {
state.list = res.data
}, () => console.log(this.state.list))
Whilst Jai's answer is correct, I also don't believe you are initialising your state correctly.
As you're using a class component you must initialise the state within the class's constructor.
constructor(props) {
super(props);
this.state = {
list: [],
refreshing: false,
};
}

REACTJS - Getting a TypeError for state variable

I'm try to get array data from a web service api (via JSON) and iterate through the response - but when trying to console.log() the state data from render(), I get this error:
TypeError:this.state.jsonStr is undefined.
class TextFields extends React.Component {
constructor() {
super();
this.state = {
}
};
componentDidMount() {
const apiURL = myConstClass.apiURL;
var apiURL_ = apiURL + '/searchIdea';
fetch(apiURL_, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
// SEND
idea_uniqueid: '',
idea_name: '',
idea_description: '', // FIX: Get from Querystring
})
}).then((Response) => Response.json()).then((findresponse) => {
// RESPONSE
this.setState({ jsonStr: findresponse, });
console.log("SEARCHIDEARESULTS"); // Stopped here - Loop through array and populate below
console.log(this.state.jsonStr);
})
}
// RENDER
// ---------------------------------------------------------------
render() {
const { classes } = this.props;
const dataStr = this.state.jsonStr;
console.log("RENDER"); // Stopped here - Loop through array and populate below
console.log(this.state.jsonStr);
console.log(this.state.jsonStr[0]);
return (
<div></div>
)
}
}
This happens because when the component is rendered for the first time the promise returned by fetch has not yet been resolved so the jsonStr is not found in the state. to fix this add jsonStr to the initial state and assign a value to it
TextFields extends React.Component {
constructor() {
super();
this.state = {
jsonStr: ''
}
}
}
or else you could also add conditions to check whether the jsonStr is set
TextFields extends React.Component {
constructor() {
super();
this.state = {
jsonStr: undefined
}
}
render() {
const { classes } = this.props;
const dataStr = this.state.jsonStr;
console.log("RENDER");
if (this.state.jsonStr) {
console.log(this.state.jsonStr);
console.log(this.state.jsonStr[0]);
}
return (
<div></div>
)
}
}
}
Initially this.state.jsonStr will be undefined. So when executing render() method this.state.jsonStr[0] will throw error.
Try changing console.log(this.state.jsonStr[0]); to console.log(this.state.jsonStr && this.state.jsonStr[0]);
Most of the answers touch on default state values or the error in render. But I see another error in your data fetching function.
From official documentation:
Calls to setState are asynchronous - don’t rely on this.state to reflect the new value immediately after calling setState.
In your componentDidMount method, you are making that mistake:
this.setState({ jsonStr: findresponse, });
console.log("SEARCHIDEARESULTS"); // Stopped here - Loop through array and populate below
console.log(this.state.jsonStr);
If you want to debug, I highly recommend the devTools extension, or put your console.log statements either as a callback to setState or in componentDidUpdate lifecycle.
Few notes on the advice for your render method, Do think of what your component states can be. For e.x. if fetching data from an external service, you may have the following states:
Loading
Error
No Data Found
Data Found
When you do: const dataStr = this.state.jsonStr ? this.state.jsonStr : "", you may lose the possibility to distinguish between these states.
I would advice to make that state state explicit (there are many techniques out there, I am leaving them out for brevity), but at the least I would suggest intelligent defaults. For e.x.:
constructor() {
super();
this.state = {
jsonStr : undefined
}
};
Your api call will either return value, null or an empty Array (if it is an array). May also throw an error, which you can catch with componentDidCatch.
You can now handle these cases in your render method easily.
Looks like you're trying to access to this.state.jsonStr before you define it and as a result you get the error:
TypeError:this.state.jsonStr is undefined.
Try setting it in the constructor:
constructor() {
super();
this.state = {
jsonStr: ''
}
};
Please try this modified code:
render() {
const { classes } = this.props;
const dataStr = this.state.jsonStr ? this.state.jsonStr : "" ;// I made a change here
console.log("RENDER"); // Stopped here - Loop through array and populate below
console.log(dataStr);
return (
<div></div>
)
}
}

can't access object's properties within object in react

I can't seem to access data that's part of an object within an object. here I'm trying to access likes in profile which would otherwise be fine using vanilla javascript to print out this.state.data.profile.likes
class App extends Component {
constructor(props) {
super(props);
this.state = {
data: {}
};
}
componentDidMount() {
var x = {
"notifications": 12,
"profile": {
"likes": 5
}
};
this.setState({
data: x
});
}
render() {
const {notifications, profile} = this.state;
return (
<div>
<span>Notifications {notifications}</span>
<span>Likes {profile.likes}</span>
</div>
);
}
Before mounting - and on the initial render - your state looks like this:
{
data: {}
}
After mounting - on the second render - your state looks like this:
{
data: {
notifications: 12,
profile: {
likes: 5
}
}
}
You're trying to access this.state.profile.likes which doesn't exist. Presumably you mean to access this.state.data.profile.likes which does exist, but only on the second render.
I noticed this while also trying to fix the same problem. The constructor should be:
constructor(props) {
super(props);
this.state = {
data: {
profile: {}
}
};
}
Always initialize objects within objects
https://reactjs.org/docs/react-component.html#mounting
render -> componentDidMount
put state data in constructor

Is this React and Axios then promise usage correct?

I am using axios for a React project, and I was wondering if the usage of then promise is correct in this case.
Basically, I use axios to fetch data from the database when the component renders.
class Participants extends React.Component{
constructor(props){
super(props);
this.state = {
databaseUsers: [],
}
this.getUsers = this.getUsers.bind(this);
}
getUsers(){
var users = axios.get('/users/get-users').then((response) => {
this.setState({databaseUsers: response.data});
});
}
componentWillMount(){
this.getUsers();
}
render(){
console.log(this.state.databaseUsers);
return(** html tree **);
}
}
What I observe is that the state of the component is set twice, once when the rendering occurs, and the then promise fires, and a second time when the promise is done fetching the data from the database and sets the state.
How do I get more control over this? Like actually wait for the data on the database, and then render?
Any tips are welcome.
There are other ways to implement what you did with several components.
But let's stick to this example.
There is nothing wrong to rendering twice, as you don't want to wait for the response and then display output.
You can have a loading flag so you could show a "loading" code and when loaded show the output.
Or you can have 1 parent component that manages the work:
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
loading: true,
data: []
}
}
componentDidMount() {
this.setState({loading: true})
axios.get('/users/get-users').then((response) => {
this.setState({
loading: false,
data: response.data
})
});
}
render() {
if (this.state.loading) {
return <LoadingComponent />;
}
return <DataComponent data={this.state.data} />
}
}

Resources