Trivial issue with React setState function - javascript

I am actually at a loss to figure out why this isn't working as I have spent a lot more hours than usual on how to get it fixed. The problem is I am using axios to make a REST call to get the data to be rendered. Inside the block to handle the response, even though I am able to retrieve the data the 'this' object somehow fails to refer to the correct object and I get an error. I dono why this is happening but any help on it will be highly appreciated.
Posting my code snippet below. I have tried saving the context of this outside the axios call scope and used the new variable but that too does not help. Here is the error I get in my console
TypeError: _this2.setState is not a function
import React, {Component} from 'react';
import axios from 'axios';
import './RouteList.css';
class RouteList extends Component{
constructor(){
super();
this.setState = {
allRoutes: {},
selectedRoutes: {}
};
}
componentDidMount(){
const that = this;
//Retrieve the SF-Muni route list
axios.get('http://webservices.nextbus.com/service/publicJSONFeed?command=routeList&a=sf-muni')
.then(response => {
console.log(response);
that.setState({ allRoutes: response.data.routes });
})
.catch((error) => {
console.log(error);
});
}
render(){
return (
<div className="transit-routes">
{/*TODO-Code to render data.*/}
</div>
);
}
}
export default RouteList;`

The problem is that you are overwriting the setState method in the constructor, try to set the initial state like this:
this.state = {
allRoutes: {},
selectedRoutes: {}
};
Also, when using an arrow function, there's no need to save the parent scope, the function will run under the same scope as the outer function.

Related

How to deal with asynchronous props and state

I can't quite wrap my head around this.
I'm having to pass data that's fetched asynchronously. Issue is, the props are asynchronous as well. Here's a simplified version of the component:
import React, { Component } from 'react'
import CSVLink from 'react-csv'
import generateData from './customApi/generateData
type Props = { job?: JobType | undefined }
type State = { csvData: string[][] }
class MyComponent extends Component<Props, State> {
state = {
csvData = [],
}
generateCsv = async (jobId: string) => {
const csvData = await generateData(jobId)
this.setState({ csvData })
}
async componentDidMount() {
const { job } = this.props
await this.generateCsv(job.id)
}
render() {
const { csvData } = this.state
return (
<CSVLink data={csvData}>
<p>Download csv</p>
</CSVLink>
)
}
}
export default connectFirestore(
(db, params) => ({ getJob(db, params.id) })
)
Basically my props are fetched from an API call to firestore, so it takes a while to load the job. Issue is, when I'm trying to pass the jobId inside the async componentDidMount(), it ends up passing undefined, because the job props are not loaded yet.
I don't link the whole passing state to async call business going on, but I can't think of any other way, how I would passing the csvData from the generateDate() async call only once the Promise is resolved.
I guess the easiest way to approach this would be, to perhaps somehow check if the props inside the componentDidMount() are already fetched?
What would be the correct way to approach this?
You are missing to implement the constructor where the props are set
constructor(props){
super(props);
this.state = {
csvData = [],
};
}
componentDidMount(){
//This will work
console.log(this.props.job);
};
If job property should by async, do it async: rather than passing a value which will change in future, you can pass a Promise which resolves with the id, than change your componentDidMount as follows:
componentDidMount() {
const id = await this.props.job;
this.generateCsv(id)
}
Hope this helps.
Maybe you should change the code inside your parent component, I imagine that you are making the API call there, as you are passing this data as props in this component you are showing to us.
I also imagine that you are making the API call with the fetch command, which can have a .then(()=>{}) method triggered when the API call finished loading, after that you can change the state of that component carrying the API fetched data and THEN render this child. Something I used recently for my project was to load from API, update state and conditionally render the child component, which will not know I made an API call because I am passing already loaded data. Normally while it is waiting I put something like this:
if(this.state.dataFetched == null)
return(<h1>Loading page...</h1>)
else return <childComponent loadedData = {this.state.dataFetched}/>
And then access that data as this.props.loadedData
Not sure if it's the optimal solution, but it works.
I've decided to use componentDidUpdate() life-cycle method, where I'm comparing whether the props have already update and once they did, I'm calling the asynchornous function for generating the csv data.
async componentDidUpdate(prevProps: Props) {
if (prevProps !== this.props && !!this.props) {
const { job } = this.props
if (job) {
await this.generateCsv(job.id)
}
}
}
This way we generate new data every time the data inside the props.job changed and also we don't attempt to call generateCsv() on undefined while it's still being fetched from firestore.

ReactJS error getting object assigned from axios call to api

I have the following code:
import React, { Component } from 'react';
import axios from 'axios';
class Dashboard extends Component {
state = {
name : 'randomname',
apiData: {}
};
componentDidMount() {
axios.get('https://api_url/getdata)
.then(res => {
const apiData = res.data
this.setState({apiData});
});
}
render() {
const { name, apiData} = this.state;
//THIS WORKS
var objTest = [{game_id: 2}]; //This is returned from apical
console.log(objTest[0].game_id);
console.log(apiData); //logs the following: [{game_id: 2}]
console.log(apiData[0]); //logs the following: {game_id: 2}
console.log(apiData[0].game_id); //Error occurs See error below
return (
<div className="wrap">
TESTING
</div>
);
}
}
export default Dashboard;
While testing and trying to get game_id using: console.log(apiData[0].game_id); I get the following error:
TypeError: undefined is not an object (evaluating
'apiData[0].game_id')
I would like to know why this works when I declare a variable and assign it the same values as the api call returns. But it does not work then I'm assigning the api call to apiData. It can only access apiData[0] which returns {game_id:2} , but cannot access apiData[0].game_id.
Thanks for all the help!
The main issue here is the order of life cycle methods. During mounting phase the constructor and then the render method is called. ComponentDidMount is not called yet and hence your state is empty. The reason you are not getting error when you log apiData or apiData[0] is it simply loggs empty array or object during initial render call (mounting phase) and then the actual object during the second render after componentDidMount(updateing phase). But when you try to call the property(game_id), you get an error(undefined) during the mounting phase since you are calling it on an empty array/object.
The solution is check for the existance of the parent object before calling the property on it , for example , usine optional chaining (JS2020 new future) which checks apiData[0], the error should be fixed just py appending "?" after the object. you can also use other methods for older JS.
console.log(apiData[0]?.game_id)
ComponentDidMount is triggered after the render method has loaded. Which means that console.log(apiData[0]) is calling the default state first before componentDidMount method is called.
Default state is an empty object here and not an array. So the index 0 of apiData is nothing. Changing the default state to apiData: [{game_id: null}] will give you the result and state will change once the componentDidMount is triggered and the api is successfully called.
This however is not the best approach. It's just to make things clear and understandable.
Simply defined one flag in the state and check whether your data is available or not once you get data change that flag and and load your component element accordingly.
see below solution for your problem statement.
import React, { Component } from 'react';
import axios from 'axios';
class Dashboard extends Component {
state = {
loading:true,
name : 'randomname',
apiData: {}
};
componentDidMount() {
axios.get('https://api_url/getdata').then(res => {
this.setState({apiData:res.data, loading:false});
});
}
render() {
const { name, apiData, loading} = this.state;
//THIS WORKS
var objTest = [{game_id: 2}]; //This is returned from apical
console.log(objTest[0].game_id);
console.log(apiData); //logs the following: [{game_id: 2}]
console.log(apiData[0]); //logs the following: {game_id: 2}
console.log(apiData[0].game_id); //Error occurs See error below
return (
<div className="wrap">
{loading ? <div>Loading ...</div> : <div className="wrap">TESTING</div>}
</div>
);
}
}
export default Dashboard;

reactjs check for data update

I have GUser class, in my project to store data from GitHub-api and process them :
import axios from "axios";
export default class GUser {
constructor(userName) {
this.userName=userName;
this.getUserData(this,userName);
}
getUserData(that, userName) {
if(that.data===undefined)
{
axios.get('https://api.github.com/users/'.concat(userName))
.then(function (response) {
that.data = response.data;
console.log(that);
return that.data;
})
.catch(function (error) {
console.log(userName+ " got error " +error);
});
}
else{return that.data;}
}
}
I'm trying to sync pages, so in my TestRender I wrote :
import React from 'react';
import GUser from "../model/GUser";
class TestRender extends React.Component{
userName='maifeeulasad';
user=new GUser(this.userName);
componentWillMount() {
try {
console.log(this.user.getUserData());
}
catch (e) {console.log(e);}
}
componentDidMount() {
try {
console.log(this.user.getUserData());
}
catch (e) {console.log(e);}
}
render() {
return(
<div>
</div>
);
}
}
export default TestRender;
My target is to console log the data, as soon as it gets received.
How can I do this ?
Previously I used componentWillMount only, when I loaded data from the same script. It's currently giving me TypeError: Cannot read property 'data' of undefined, maybe because of I haven't specified data as state in GUser, but it gets created through time right ?
Looks like you are using object's attribute to store the state's component, that isn't good, because React will not to re-render the component when these values are updated.Every instance of TestRender should have the same value of userName and user as attribute? These values are always the same? If not, use useState for that.
Also, componentDidMount and componentWillMount will be called only when the component is being mounted, and at this moment you didn't finished the fetch, so you don't have yet the value to print. It's an async problem.
To handle with async challenges, I recommend to use useEffect.

React js, print out react component's state is not empty, but when call this.state, all data gone

In the React component's componentDidMount() I make an axios get request to receive a response and setState to the component. The response is correct, and when print out the component object in the component class with this, the object looks good. Then I call console.log(this.state), then every property of the component become empty. Why this happens? How can I get the state's property?
MyComponent.js
React component did mount method:
componentDidMount() {
getLastWeek(this); // here I make a get request
console.log('===');
console.log(this); // this line prints out an object will all the properties
console.log(this.state); // all properties of state disappear
}
The get request used above:
service.js
...
function getLastWeek(component) {
const lastWeek = getEndpoint(7);
Axios.get(lastWeek)
.then(res => {
const bpi = res.data.bpi;
const prices = Object.values(bpi);
component.setState({ lastWeek: prices });
})
.catch(err => {
console.log(err);
});
}
...
You are making an axios request which is an asynchronous function, so what is happening is you are using console.log(this.state) before the state gets set.
The render() method gets executed every time the state changes so if you put your console.log inside the render() method you should now see how your state change. Something like this:
class Example extends Component {
constructor() {
...
}
componentDidMount() {
...
}
render() {
console.log(this.state);
return(...);
}
}

use axios with react to get data from api

I am trying to use axios to get data from the api (https://reqres.in/) and display in my react app. Before this I fetched the data from the API using fetch method in javascript. Now I have tried coding this from various resources. How should I do it. Is it the correct method?
My app.js file-
import React, { Component } from 'react';
import './App.css';
import axios from 'axios';
class App extends Component {
constructor(props) {
super(props);
this.successShow = this.successShow.bind(this);
this.errorShow = this.errorShow.bind(this);
}
componentDidMount() {
axios.get('https://reqres.in/api/products/3')
.then(function (response) {
this.successShow(response);
})
.catch(function (error) {
this.errorShow(error);
});
}
successShow(response) {
this.member = <pre>{JSON.stringify(response.data, null, '\t')}</pre> ;
}
errorShow(error) {
this.member = <pre>{JSON.stringify(error.response.data, null, '\t')}</pre>;
}
render() {
return (
<div>
<h2>Welcome to React</h2>
<h3>{JSON.stringify(this.state.person.data)}</h3>
<div>{this.member}</div>
</div>
);
}
}
export default App;
It also gives the error - Unhandled Rejection (TypeError): Cannot read property 'errorShow' of undefined.
Changes:
1. You need to bind this with then and catch callback methods, use arrow functions.
2. You didn't define the initial state and using this.state.person.data it will throw error.
3. Storing the UI in state or global variable is not a good idea, ui part should be inside render method only.
Write it like this:
class App extends Component {
constructor(props) {
super(props);
this.state = {
person: {}
}
//this.successShow = this.successShow.bind(this);
//this.errorShow = this.errorShow.bind(this);
}
componentDidMount() {
axios.get('https://reqres.in/api/products/3')
.then((response) => {
this.successShow(response);
})
.catch((error) => {
this.successShow(error);
});
}
successShow(response) {
this.setState({
person: response.data
});
}
render() {
return (
<div>
<h2>Welcome to React</h2>
<h3>{JSON.stringify(this.state.person.data)}</h3>
<pre>{JSON.stringify(this.state.person.data)}</pre>
<div>{this.member}</div>
</div>
);
}
}
When you call this.errorShow() inside of the function, this is not your component object, but context of function. You should use arrow functions instead, arrow functions do not create its own this so you can access your component this:
componentDidMount() {
axios.get('https://reqres.in/api/products/3')
.then((response) => {
this.successShow(response);
})
.catch(error) => {
this.errorShow(error);
});
}
More info about arrow functions
Try this:
componentDidMount() {
axios.get('https://reqres.in/api/products/3')
.then((response) => {
this.successShow(response);
})
.catch((error) => {
this.errorShow(error);
});
}
Use arrow functions to remain the right scope of this
The problem is that the this in your then and catch callbacks doesn't refer to your class, but to the default (global) scope. You need to bind the right this. You actually already have the appropriate functions set up with this binding, so you can just use them directly:
componentDidMount() {
axios.get('https://reqres.in/api/products/3')
.then(this.successShow)
.catch(this.errorShow);
}
In general, you can also use => function syntax, which inherits the 'this' from the scope the function is declared in, rather than using the global scope. E.g.
componentDidMount() {
axios.get('https://reqres.in/api/products/3')
.then(success => this.successShow(success))
.catch(error => this.errorShow(error));
}
(note the => functions are completely unnecessary here of course).
You have an additional problem, which is the you need to store member in component state (this.state.member), not just as a field, and use the setState function to update it. Otherwise your component won't re-render when you update member.

Categories