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;
Related
I want to be able to receive mqtt messages and display them on a web app. I'm working with AWS Amplify's PubSub and the function calls happen outside of the class component. I can't directly access any of the instance functions/properties from the function outside of it but I need some way to trigger a setState change so that the web app can be re-rendered, right?
I've tried just directly calling the function from the React class, but there's still no re-rendering happening. I've tried making an outside variable, storing the message received in it and then accessing the variable from the React class but still no trigger. Then I researched and found that I could force a re-render every couple of seconds but read that doing such a thing should be avoided. I should be using ComponentDidMount and setState functions but not really understanding how to get this to work.
Again all I'm really trying to do is get the message and update the web app to display the new message. Sounds pretty simple but I'm stuck.
import...
var itemsArr = [];
function subscribe() {
// SETUP STUFF
Amplify.configure({
...
});
Amplify.addPluggable(new AWSIoTProvider({
...
}));
// ACTUAL SUBSCRIBE FUNCTION
Amplify.PubSub.subscribe('item/list').subscribe({
next: data => {
// LOG DATA
console.log('Message received', data);
// GET NAMES OF ITEMS
var lineItems = data.value.payload.lineItems;
lineItems.forEach(item => itemsArr.push(item.name));
console.log('Items Ordered', itemsArr);
// THIS FUNCTION CALL TRIGGERS AN ERROR
// CANT ACCESS THIS INSTANCE FUNCTION
this.update(itemsArr);
},
error: error => console.error(error),
close: () => console.log('Done'),
});
}
// REACT COMPONENT
class App extends Component {
constructor(props){
super(props);
this.state = {
items:null,
};
}
// THOUGHT I COULD USE THIS TO UPDATE STATE
// TO TRIGGER A RE-RENDER
update(stuff){
this.setState({items: stuff});
}
render() {
// THINK SUBSCRIBE METHOD CALL SHOULDN'T BE HERE
// ON RE-RENDER, KEEPS SUBSCRIBING AND GETTING
// SAME MESSAGE REPEATEDLY
subscribe();
console.log('items down here', itemsArr);
return (
<div className="App">
<h1>Test</h1>
<p>Check the console..</p>
<p>{itemsArr}</p>
</div>
);
}
}
export default App;
Ideally, I'd like columns of the list of items to be displayed as messages come in but I'm currently getting an error - "TypeError: Cannot read property 'update' of undefined" because the subscribe function outside of the class doesn't have access to the update function inside the class.
Put subscribe method inside the App component so you can call it. You can call subscribe method in componentDidMount lifecycle to execute it (to get the items) after App component renders the first time. And then, update method will run this.setState() (to store your items in the state) causing App component to re-render. Because of this re-render, your this.state.items will contain something and it will be displayed in your paragraph.
class App extends Component {
constructor(props){
super(props);
this.state = {
items: [],
};
this.subscribe = this.subscribe.bind(this);
this.update = this.update.bind(this);
}
update(stuff){
this.setState({ items: stuff });
}
subscribe() {
// SETUP STUFF
Amplify.configure({
...
});
Amplify.addPluggable(new AWSIoTProvider({
...
}));
// ACTUAL SUBSCRIBE FUNCTION
Amplify.PubSub.subscribe('item/list').subscribe({
next: data => {
// LOG DATA
console.log('Message received', data);
// GET NAMES OF ITEMS
let itemsArr = [];
var lineItems = data.value.payload.lineItems;
lineItems.forEach(item => itemsArr.push(item.name));
console.log('Items Ordered' + [...itemsArr]);
this.update(itemsArr);
},
error: error => console.error(error),
close: () => console.log('Done'),
});
}
componentDidMount() {
this.subscribe();
}
render() {
console.log('items down here ' + [...this.state.items]);
return (
<div className="App">
<h1>Test</h1>
<p>Check the console..</p>
<p>{this.state.items !== undefined ? [...this.state.items] : "Still empty"}</p>
</div>
);
}
}
export default App;
By using an Object as a prop which stores references to internal methods of a child-component, it is possible to then access them from the parent.
The below (overly-simplified) example shows this.
The RandomNumber purpose is to generate a single random number. It's all it does.
Then, at its parent-level, some user action (button onClick event) is triggering the RandomNumber component to re-render, using a custom hook called useShouldRender, which generates a random number every time it's setter function is invoked, so by exposing the setter function to the "exposed" prop object, it is possible to interact with internal component operations (such as re-render)
const {useState, useMemo, useReducer} = React
// Prints a random number
const RandomNumber = ({exposed}) => {
// for re-render (https://stackoverflow.com/a/66436476/104380)
exposed.reRender = useReducer(x => x+1, 0)[1];
return Math.random(); // Over-simplification. Assume complex logic here.
}
// Parent component
const App = () => {
// create a memoed object, which will host all desired exposed methods from
// a child-component to the parent-component:
const RandomNumberMethods = useMemo(() => ({}), [])
return (
<button onClick={() => RandomNumberMethods.reRender()}>
<RandomNumber exposed={RandomNumberMethods}/>
</button>
)
}
// Render
ReactDOM.render(<App />, root)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="root"></div>
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(...);
}
}
After fetching the data from an API, and putting that data on the Redux state, I'm using a helper function in mapStatetoProps to select and modify part of that data and pass it modified to the props.
So without the rendering I can see in the console.log that everything goes as it should.
Empty props: this.props.pageContent = {}
The data fetch and mapped to props: this.props.pageContent = { pageSection: [{something here that I don't want}, {}, {}...] }
The data as I want it selected and passed to the props: this.props.pageContent = { pageSection: [{card: 'my Data'}, {}, {}...] }
but when I pass some propsto a component it throws an error because those props that I'm trying to pass haven't arrived yet to this.props (in this case card.cardTitle)
This is my code so far:
class Page extends Component {
componentWillMount() {
this.props.fetchPageContent();
}
render() {
console.log(this.props.pageContent)
if (!this.props.pageContent.pageSection[0].card) return null;
return (
<div>
<PageSection
introSectionCardTitle={ this.props.pageContent.pageSection[0].card.cardTitle}
introSectionCardContent={ this.props.pageContent.pageSection[0].card.cardContent}
/>
</div>
);
}
Any ideas?
before the return I tried to have an if statement with diferent options, but the error keeps the same:
TypeError: Cannot read property '0' of undefined
You have a problem here if (!this.props.pageContent.pageSection[0].card)
replace
if (!this.props.pageContent.pageSection[0].card)
with
if(this.props.pageContent && this.props.pageContent.pageSection && this.props.pageContent.pageSection[0].card)
Because you are not sure that your props has pageContent and you are also not sure that pageSection exist, because before setting the props pageContent is undefined and you are trying to access an object inside it and then find element inside an array
Try the updated code below:
class Page extends Component {
componentWillMount() {
this.props.fetchPageContent();
}
render() {
console.log(this.props.pageContent)
if(this.props.pageContent && this.props.pageContent.pageSection && this.props.pageContent.pageSection[0].card)
{
return (
<div>
<PageSection
introSectionCardTitle={ this.props.pageContent.pageSection[0].card.cardTitle}
introSectionCardContent={ this.props.pageContent.pageSection[0].card.cardContent}
/>
</div>
);
}
else
{
return (<div></div>);
}
}
I'm a total rookie when it comes to React and this is most likely a simple issue, but it nearly drove me nuts.
The code is following:
import React, { Component } from 'react';
class Tile extends Component {
constructor(props) {
super(props);
this.state = {
priceLog: [],
diff: 'equal'
};
}
componentWillReceiveProps() {
let log = this.state.priceLog;
log = log.push(this.props.price);
this.setState({ priceLog: log });
console.log(this.state.priceLog);
}
render() {
return (
<div className="Tile">
Company: {this.props.name}<br/>
Price: {this.props.price}
<div className={this.state.diff}></div>
<button id={this.props.id}>Details</button>
</div>
);
}
}
export default Tile;
I get "Unhandled Rejection (TypeError): log.push is not a function" when the component is rendered. All properties passed to the component are strings.
Besides the answer from #CD, you do not want to directly manipulate a state outside the designated setState method. In this case you could use concat to return a new array and assign that to the state variable. Something like this
this.setState({ priceLog: this.state.pricelog.concat(this.props.price)});
And your second call to the console.log might not deliver the desired results since setState is an asynchronous call. If you want to access the new state variable you have to use a callback like this
this.setState({
priceLog: this.state.pricelog.concat(this.props.price)
}, () => console.log(this.state.pricelog));
push returns the new length of the array so replace:
log = log.push(this.props.price);
with:
log.push(this.props.price);
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.