Fill an array with an object (using setState) - javascript

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

Related

React cannot read properties of undefined (reading 'state')

Updated code
Initialized constructor and placed filter and loadOptions in class render method.
Still showing error saying that this.state.cars.filter is not a function.
import React, { Component } from 'react';
import AsyncSelect from 'react-select/async';
export default class Search extends Component {
constructor(props) {
super(props);
this.state = {
inputValue: null,
cars: []
};
}
componentDidMount() {
fetch(url)
.then(res => {
this.setState(prevState => ({...prevState, cars: res}))
})
}
handleInputChange = (newValue) => {
const inputValue = newValue.replace(/\W/g, '');
this.setState({ inputValue });
return inputValue;
};
render() {
const filterCars = (inputValue) => {
return this.state.cars.filter((i) =>
i.label.toLowerCase().includes(inputValue.toLowerCase())
);
};
const loadOptions = (
inputValue,
callback) => {
setTimeout(() => {
callback(filterCars(inputValue));
}, 1000);
};
return (
<div>
<AsyncSelect
cacheOptions
loadOptions={loadOptions}
defaultOptions
onInputChange={this.handleInputChange}
/>
</div>
);
}
}
Example code of json file that Im fetching data from
[{"make":"KIA","link":"\/images\/image.jpg"},{"make":"BMW","link":"\/images\/image.jpg"}]
There are a couple of issues here. First is that you should declare state inside of a constructor, so instead of declaring state like that, do the following:
constructor(props) {
super(props)
this.state = {inputValue: '', cars: []}
}
Then you need to deal with your state mutations. Whenever you call setState, you are essentially giving it a new object which it sets the state to. You do not reassign the properties, you return a new object.
To resolve this reassignment issue, ES6 has introduced the spread operator which was not specifically designed to deal with mutations but it helps a lot.
Essentially, whenever you want to create a copy of an object, you don't do
let a = b
instead you do
let a = {...b}
With this change, changes on A will not reflect on B. So you make a copy instead of a duplicate.
How you can use this to avoid state mutation?
Whenever you call setState, make sure to first spread the rest of the state accross the new one before making any property changes:
setState(prevState => ({...prevState, cars: res}))
This way, you do not essentially remove the inputValue from your state object which can cause undefined issues.
Back to the main issue, your function filterCars is located outside of your class and in there you are trying to access this.state. Move the filterCars function inside of the class and your error will be resolved but if you don't resolve the mutation issues, it will not work as expected.

How to loop an object that is inside an array and is inside an object? React

I am doing my front in react and I am consuming microservices from laravel, there is one that the only thing it does is return a json with a list of countries, but I cannot do a cycle to access the object, I have used map and for, but nothing It works, as the microservice returns a plain text json, I first pass it through JSON.parse to create an object, and that object looks like this:
object after JSON.parse of response microservice
Here is my code of how I request using axios:
class MisDatosEmprendedor extends React.Component {
constructor(props){
super(props);
this.state = {
country:'',
}
}
async componentDidMount() {
await axios({
url: `${countryUrl}`,
method: 'GET',
})
.then(response =>{
var objson = JSON.parse(response.data);
this.setState({
country: objson,
})
})
.catch(error => {
console.log(error)
return error;
});
}
}
Now I try to use map to loop the render and show all countries in a select, but it tells me this.state.country.countries not defined error
render(){
return <React.Fragment>
{this.state.country.countries.map(function(d){
return(
<option value={d.id}>{d.name}</option>
)
})}
</React.Fragment>
}
I would initialise the state of country as an object like so country: {countries: []} not an empty string. As your component is already mounted it is reading an empty string, thus you have your error.
Your initial state does not match the data from the api. Before the request succeeds, it will throw an error. Try:
constructor(props){
super(props);
this.state = {
country: { countries: [] },
}
}

Reactjs, Cannot read property 'lessons' of null

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
})
})
}

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