Reactjs, Cannot read property 'lessons' of null - javascript

In my code I tell the API to retrieve the data from the end point inside componentDidMount().
componentDidMount() {
this.setState({
lessons: API.getAllLessons()
})
}
Then I map each item inside the list to an individual panel inside the render
render() {
return (
this.state.lessons.map((lesson)=>{
<LessonItem lesson={lesson}/>
})
);
}
However, it throws an error when mapping as the property in the state it's trying to map is null, but it shouldn't be as data is returned from the API.
Uncaught (in promise) TypeError: Cannot read property 'lessons' of null
My state gets defined like so
export interface AuthenticatedHomeState {
currentUser: any;
lessons: any;
}

you probably didn't initialised the state.
constructor(props){
super(props)
this.state = {
lessons: [] //default value
}
}
componentDidMount() {
this.setState({
lessons: API.getAllLessons()
})
}
However, if API.getAllLessons returns a Promise you will need to handle it differently.

you are calling componentDidMount, meaning it will run after render had been called. You need to call componentWillMount()

You have to intialise the state first, then call the api, and if the api call is a promise call(which usually is) you will have to read the values in such a way,
constructor(props){
super(props)
this.state = {
lessons: [] //set default value for lessons
}
}
componentDidMount() {
API.getAllLessons().then((resp)=>{
this.setState({
lessons: resp.data
})
})
}

Related

Fill an array with an object (using setState)

I am trying to fill an array with an array of objects that was returned from a axios request. However, the array is returned empty.
export default class Todo extends Component {
constructor(props){
super(props)
this.state = { description: '', list: [] }
this.handleChange = this.handleChange.bind(this)
this.handleAdd = this.handleAdd.bind(this)
this.refresh();
}
refresh() {
axios.get(`${URL}?sort=-createdAt`)
.then(resp => this.setState({...this.state, description: 'Qualquer valor', list: resp.data}))
//.then(resp => console.log(resp.data))
console.log(this.state.list)
}
I initialize the array in the constructor (named "List") and then, following the refresh function, whcih is called as soon as the page loads, I receive the response of the axios request, and try to fill the "list" array with the data returned values, but it doesn't work.
Obs: I already guaranteed that the request is returning well and the "resp.data" contains the data that I want to push to the array named "list" (the response is returning an array of objects)
If you call function from constructor and in that function try to update state than react will not throw warning and will not update state.
So instead of calling function in constructor, try to call function in componentDidMount like below and try to access updated state value in callback function:-
constructor(props){
super(props)
this.state = { description: '', list: [] }
this.handleChange = this.handleChange.bind(this)
this.handleAdd = this.handleAdd.bind(this)
}
componentDidMount() {
this.refresh();
}
refresh() {
axios.get(`${URL}?sort=-createdAt`).then(resp =>
this.setState(
{
description: 'Qualquer valor',
list: resp.data
},
() => {
console.log(this.state.list); // here this will print updated list
}
)
);
}
The axios request call has to go in the componentDidMount life cycle hook, not the constructor.
Please refer to the documentation for more details: https://reactjs.org/docs/react-component.html#componentdidmount

React error - Cannot read property 'setState' of undefined

Below code returns an error for me
Uncaught (in promise) TypeError: Cannot read property 'setState' of undefined
I am new to react and this seems very basic. Any suggestion what could I be doing wrong. From the json result I want to store all names in my array.
import React, { Component } from "react";
class Search extends Component {
constructor(props) {
super(props);
this.state = {
list: [],
}
}
Search() {
fetch("http://url/getSearchResults")
.then(res => res.json())
.then(
(res) => {
this.setState({
list: res.data.name
})
})
}
This is a very common problem with classes in React - there are two ways to solve this problem:
Bind your methods in the constructor:
constructor(props) {
super(props);
this.state = {
list: [],
}
this.Search = this.Search.bind(this);
}
Use an arrow function instead:
search = () => { ... }
See this post for more information.
Note: using componentDidMount() will be useful if you are trying to make the fetch call on mount - the answer above addresses the issue of this being undefined as per the error you are seeing.
Add componentDidMount() to Search(), it is invoked immediately after a component is mounted (inserted into the tree). Initialization that requires DOM nodes should go here.Its a good place to load data from a remote endpoint.

Can not access properties in JavaScript object array (React.js)

I have a React.js component which pulls its initial state data from an API call in componentDidMount(). The data is an array of objects.
I am able to view the array, and individual elements using JSON.stringify (for debugging), but when I try to access a property in an element, I get an error which seems to imply that the element is undefined, despite having checked that it is not.
Code:
class TubeStatus extends Component {
constructor(props) {
super(props);
this.state = { 'tubedata' : [] };
};
componentWillMount() {
let component = this;
axios.get('https://api.tfl.gov.uk/line/mode/tube/status')
.then( function(response) {
component.setState({ 'tubedata' : response.data });
})
.catch( function(error) {
console.log(JSON.stringify(error, null, 2));
});
};
render() {
return (
<div><pre>{this.state.tubedata[0].id}</pre></div>
);
}
}
Error:
Uncaught TypeError: Cannot read property 'id' of undefined
If I use JSON.stringify() to display this.state.tubedata, all the data is there.
In my admittedly limited knowledge of React.js, I suspect this is because React.js is trying to access the .id property before componentDidMount() fires loading the initial state data, thus returning undefined, but I could be completely wrong.
Anyone able to point me in the right direction?
As you are fetching data from API call, on initial rendering data is not available hence you are getting error.
this.state.tubedata.length>0// check if tubedata is available
So,
render() {
return (
<div><pre>{this.state.tubedata.length>0?this.state.tubedata[0].id:null}</pre></div>
);
}
this is because you are having a async request and since the state array is initially empty this.state.tubedata[0] is initially undefined
Keep a check before using id like
<div><pre>{this.state.tubedata.length > 0 && this.state.tubedata[0].id}</pre></div>
class TubeStatus extends React.Component {
constructor(props) {
super(props);
this.state = { 'tubedata' : [] };
};
componentWillMount() {
let component = this;
};
render() {
return (
<div>Hello <pre>{this.state.tubedata.length > 0 && this.state.tubedata[0].id}</pre></div>
);
}
}
ReactDOM.render(<TubeStatus/>, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>

Can't set state in componentWillMount

I am creating a simple chat app where I make an api call to my database via axios which returns an array of message objects. I am able to get the data when I make an axios call in componentWillMount. Then I am trying to setState to display the conversation. Here's the code:
export default class Chat extends Component {
constructor(props){
super(props);
this.state = {
messages : [],
message : '',
};
this.socket = io('/api/');
this.onSubmitMessage = this.onSubmitMessage.bind(this);
this.onInputChange = this.onInputChange.bind(this);
}
componentWillMount() {
axios.get(`api/messages`)
.then((result) => {
const messages = result.data
console.log("COMPONENT WILL Mount messages : ", messages);
this.setState({
messages: [ ...messages.content ]
})
})
};
I have seen some posts concerning lifecycle functions and setting state, and it seems like I'm doing the right thing.
Again to highlight, axios call working fine, setting the state is not working. I am still seeing an empty array. Thanks in advance!
EDIT: Here is the solution to my issue specifically. It was buried in a comment, so I thought I'd leave it here..
"I discovered the issue. It was actually in how I was parsing my data. The spread operator on ...messages.content didn't work because messages.content doesn't exist. messages[i].content exists. So my fix was to spread just ...messages Then in a child component I map over the objects and parse the .content property. Thanks for the help guys!"
In your case, your setState() won't work because you're using setState() inside an async callback
Working Fiddle: https://jsfiddle.net/xytma20g/3/
You're making an API call which is async. So, the setState will be invoke only after receiving the data. It does not do anything with componentWillMount or componentDidMount. You need to handle the empty message in your render. When you receive your data from the API, set that data to the state and component will re-render with the new state which will be reflected in your render.
Pseudo code:
export default class Chat extends Component {
constructor(props){
super(props);
this.state = {
messages : [],
message : '',
};
this.socket = io('/api/');
this.onSubmitMessage = this.onSubmitMessage.bind(this);
this.onInputChange = this.onInputChange.bind(this);
}
componentWillMount() {
axios.get(`api/messages`)
.then((result) => {
const messages = result.data
console.log("COMPONENT WILL Mount messages : ", messages);
this.setState({
messages: [ ...messages.content ]
})
})
render(){
if(this.state.messages.length === 0){
return false //return false or a <Loader/> when you don't have anything in your message[]
}
//rest of your render.
}
};
componentWillMount() is invoked immediately before mounting occurs. It
is called before render(), therefore setting state in this method will
not trigger a re-rendering. Avoid introducing any side-effects or
subscriptions in this method. docs
So, You need to call componentDidMount as-
componentDidMount() {
axios.get(`api/messages`)
.then((result) => {
const messages = result.data
console.log("COMPONENT WILL Mount messages : ", messages);
this.setState({
messages: [ ...messages.content ]
})
})

Data Doesn't Update From API in React/Flux App

I'm using React + Flux on the frontend for a project and I need to get the username to display it on the sidebar.
The problem: I call the action in the constructor of the es6 class which fetches the data needed from the API, and I can see it being logged to the console from the onUserStateChanged method, but it doesn't get updated in the state within the render method. I don't understand how I can get the state change reflected in the component.
export default class extends React.Component {
constructor() {
super();
UserActions.getCurrentUser();
this.state = {
user: {}
};
}
componentDidMount() {
UserStore.addChangeListener(this.onUserStateChange);
}
componentWillUnmount() {
UserStore.removeChangeListener(this.onUserStateChange);
}
onUserStateChange() {
console.log('called');
this.state = {
user: UserStore.getCurrentUser()
};
console.log(this.state);
}
render(){
var _this = this;
return (
console.log(this.state);
<div>{_this.state.user.username}</div>
);
}
}
The call to console.log from onUserStateChange() contains the correct state back from the API whereas the console.log in render just shows a blank JS object
You probably want to use setState
As documentation says:
NEVER mutate this.state directly, as calling setState() afterwards may replace the mutation you made. Treat this.state as if it were immutable.
Also your constructor seems strange, do you really intend to not use the result of UserActions.getCurrentUser(); in
UserActions.getCurrentUser();
this.state = {
user: {}
};

Categories