ReactJs: Fetch, transform data with function, then setState - reactjs

I have managed to fetch data from an API successfully. Data transformation of JSON format works too, but i'm having trouble integrating it to "componentDidMount" to set state with a transformed JSON format. I'm getting an undefined state when i console.log(this.state.races).
I'm also getting this error message:
Can't call setState (or forceUpdate) on an unmounted component.
class Races extends Component {
constructor(props) {
super(props);
this.state = {
races: []};
this.processResults = this.processResults.bind(this);
}
componentDidMount(){
fetch(RACE_SERVICE_URL)
.then(results => results.json())
.then(this.processResults)
}
processResults(data) {
const raceId_arr = data.map(d => d.raceId);
const season_arr = data.map(d => d.season);
const raceName_arr = data.map(d => d.raceName);
const url_arr = data.map(d => d.url);
const data_mapped = {'raceId': raceId_arr, 'season': season_arr, 'raceName': raceName_arr, 'url': url_arr};
this.setState({races:data_mapped});
console.log(data_mapped);
console.log(this.state.races);
}
render() {
const title = 'Race Tracks';
return (
<div>
<h2>{title}</h2>
<RacesViz data= {this.state.races.raceId} />
</div>
);
}
}
export default Races;
I have also tried:
.then(data => this.processResults(data))
What console.log(data_mapped) prints:
{raceId:[1, 2, 3]
raceName:["AGP", "BGP", "CGP"]
season: [2018, 2018, 2018]
url: ["http://en.wikipedia.org/wiki/AGP", "http://en.wikipedia.org/wiki/BGP", "http://en.wikipedia.org/wiki/CGP"]}

setState is async so you can't get immediate result with console.log like you did. Use a callback function instead:
this.setState({races:data_mapped}, () => console.log(this.state.races));
Or you can console.log your state in your render method.

Quote from official docs:
Think of setState() as a request rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately.
Important!
This makes reading this.state right after calling setState() a potential pitfall.
So you will not get state immediately after setState. You have 2 ways to solve it.
1) You should check in componentDidUpdate hook.
componentDidUpdate(prevProps, prevState) {
console.log(this.state.races);//your data updated here.
}
You can see here to use properly.
2) Or you use callback in setState like this setState(updater, callback):
this.setState({races:data_mapped}, () => {
console.log(this.state.races)//your data updated here.
})

Related

setState is not updating in my other method

I'm very new to react and i'm confused why my state is not updated in another method of mine see example below.
fetchMovies = () => {
const self = this;
axios.get("https://api.themoviedb.org/3/trending/movie/day?api_key=XXXXXXX")
.then(function(response){
console.log(response.data)
self.setState({
collection: response.data.results
})
console.log(self.state.collection)
});
}
makeRow = () => {
console.log(this.state.collection.length);
if(this.state.collection.length !== 0) {
var movieRows = [];
this.state.collection.forEach(function (i) {
movieRows.push(<p>{i.id}</p>);
});
this.setState({
movieRow: movieRows
})
}
}
componentDidMount() {
this.fetchMovies();
this.makeRow();
}
When inside of fetchMovies function i can access collection and it has all the data but this is the part i can't understand in the makeRow function when i console log the state i would of expected the updated state to show here but it doesn't i'm even executing the functions in sequence.
Thanks in advance.
the collection is set after the async call is resolved. Even though makeRow method is called after fetchMoview, coz of async call, u will never know when the call will be resolved and collection state will be set.
There is no need to keep movieRows in the state as that is just needed for rendering. Keeping html mockup in the state is never a good idea.
So u should just call fetchMoviews in the componentDidMount and render the data in as follows:
render() {
const { collection } = this.state;
return (
<>
{
collection.map(c => <p>{c.id}</p>)
}
</>
)
}
make sure the initial value for collection in the state is [] .
The setState() documentation contains the following paragraph:
Think of setState() as a request rather than an immediate command
to update the component. For better perceived performance, React may
delay it, and then update several components in a single pass. React
does not guarantee that the state changes are applied immediately.
To access the modified state you need to use the function signature setState(updater, [callback]), so in your case it should be;
self.setState({
collection: response.data.results
}, () => { // Will be executed after state update
console.log(self.state.collection)
// Call your make row function here and remove it from componentDidMount if that is all it does.
self.makeRow()
} )

When should React call AJAX request after props change?

I used to call AJAX after props change by ComponentWillReceiveProps()
componentWillReceiveProps(nextProps) {
if(nextProps.foo !== this.props.foo){
//fetch & call this.setState() asynchronously
}
}
After React 16.3 ComponentWillReceiveProps() is going to be deprecated in the future. Instead of ComponentWillReceiveProps() there is a new function getDerivedStateFromProps, but I can't update state async.
static getDerivedStateFromProps(nextProps, prevState) {
// I can't get access to this.props but only state
if(nextProps.foo !== this.props.foo){
//fetch & call this.setState() asynchronously
}
// I can only return a state but update it after an AJAX request
return {}
}
What's the best practice to do it.
You should not use getDerivedStateFromProps lifecycle for making api calls. Instead, use componentDidUpdate to make api call and once you get the api response do this.setState. Also as pointed in another answer, you cannot use this in static method.
componentDidUpdate(prevProps) {
if (this.props.myData !== prevProps.myData) {
this.callMyApi();
}
}
callMyApi() {
fetch("/api")
.then(response => {
this.setState({ ... });
})
}
If you are writing new component, you can also consider to write a Functional component and use useState and useEffect to trigger api call when a propis updated.
Like this:
...
const {page} = this.props;
const [images, setImages] = useState([]);
useEffect(() => {
fetch(`/myfavApi`)
.then(data => data.json())
.then(images => {
setImages(images.concat(images));
});
}, [page]); // provide page(prop) as dependency.
...
The best place for do asynchronous calls is componentDidUpdate(prevProps, prevState, snapshot) {}. getDerivedStateFromProps is static method so it hasn’t access to component instance (hasn’t access to this)

React setState won't update the view of my table

I am having hard time changing the view of the Bootstrap table, even though the state is being updated.
I did some research and found that this.setState is async, so I made sure to check and change the state in the callback function; however, the state is changed even at the callback function. I am confused if this is still a this.setState problem.
export class EduInfo extends React.Component {
constructor(props){
super(props);
this.state = {
education: this.props.incomingClass.degreeEdu
};
this.updateEdu = this.updateEdu.bind(this);
}
updateEdu = (e) => {
e.preventDefault();
let newEdu = {...this.props.incomingClass};
BackEndRestService.updateEdu(newEdu).then(data => {
this.setState({
education: data.degreeEdu,
}, () => console.log(data));
}).catch(e => {
console.log(e);
});
}
render(){
return(
<BootstrapTable
hover
condensed={true}
bootstrap4={true}
keyField={'id'}
data={this.state.education}
columns={this.columns}
/>
);
}
}
Once the state is updated, it should be re-rendering and updating the 'data={this.state.education}' in the Bootstrap table. However, the table view is not changing.
At your return function have something like:
return(
{ (this.state.readyToShow)?
<div>
<BootStrapTable ...
/>
</div>
: ''
} );
After the state of ReadyToShow is set you should be able to see the Bootstrap table with the info.
And change the state of the state readyToShow (possibly using a callback) only at the end of the response of the request you sent for the data. The problem I see is that your data might not be arriving before react renders. This situation happened to me a lot of times. For example, if using Axios to get data:
val axios4data = axios.get(*some link to the controller to get data*).then(function (response) {
...
*make something with the response and set the state of the varable for the data table of the bootstrap table*
self.setState({education: `dataFromResponse`},
() =>
{
*execute call back or code to set the state of readyToShow to true*
}
)
});
it is important to make the state of ReadyToShow be updated after the state for education was set using the call back for the setState.

State not changing after calling this.setState

I am getting the data from my form component and trying to set the state of my app component with this data.
However, the state.data is an empty object and is not updating the data. I console log the model data before setting it to check if it exists. Their is data within the model.
import React, { Component, Fragment } from "react";
import Form from "../components/Form";
import product from "./product.json";
class App extends Component {
constructor() {
super();
this.state = {
data: {}
};
}
onSubmit = (model) => {
console.log("Outer", model);
this.setState({
data: model
});
console.log("Form: ", this.state);
}
render() {
const fields = product.fields;
return (
<Fragment>
<div>Header</div>
<Form
model={fields}
onSubmit={(model) => {this.onSubmit(model);}}
/>
<div>Footer</div>
</Fragment>
);
}
}
export default App;
setState() is an async call in React. So you won't likely get the updated state value in the next line. To check the updated value on successful state update, you could check in the callback handler.
Change this
onSubmit = (model) => {
console.log("Outer", model);
this.setState({
data: model
});
console.log("Form: ", this.state);
}
to
onSubmit = (model) => {
console.log("Outer", model);
this.setState({
data: model
}, () => {
console.log("Form: ", this.state);
});
}
As per the react docs, setState is an asynchronous call. You can ensure your state has updated to perform a particular action in two ways as shown below:
You can pass the setState a function which will have your current state and props and you the value you return will be your next state of the component.
Keep in mind following:
state is a reference to the component state at the time the change is
being applied. It should not be directly mutated. Instead, changes
should be represented by building a new object based on the input from
state and props.
Following is an example:
this.setState((state, props) => {
//do something
return {counter: state.counter + props.step};
});
You can pass a callback to the setState function as mentioned in Dinesh's
answer. The callback will be executed once the state has been updated successfully hence ensuring you will have the updated state in the call back.
Following is an example:
this.setState({ ...new state }, () => {
// do something
});
Hope it helps.
I just want to add, that if you will do like this its not going to work:
this.setState({things} , console.log(this.state))
You have to pass a refarence to the call back and not the exscutable code itself. If you won't do so, the function will envoke before the state is updated,even you will see the log.

React component is rendering but not updating when state is updating

React component is showing data when state is null but, when its getting data then its not updating the content of the view.
constructor(props){
super(props);
this.state = {
post: null
}
this.getTotalDownloadSize = this.getTotalDownloadSize.bind(this);
}
componentWillMount(){
const {match} = this.props;
const postId = _.get(match, 'params.id');
getDownloadInfo(postId).then((response) => {
this.setState({
post: _.get(response, 'data')
});
}).catch((err) => {
console.log("an error while fetching data", err);
})
}
inside my render i am getting null value for render(){
render(){
const {post} = this.state;
console.log{post};
const files = _.get(post, 'files', []);
)
initially its showing the null value but after it has value but its not updating the content of the view.
can anyone help me with this.
thanks in advance.
componentDidMount is place where you can place request logic.
componentDidMount() {
const {match} = this.props;
const postId = _.get(match, 'params.id');
getDownloadInfo(postId).then((response) => {
this.setState((state) => ({ post: _.get(response, 'data')}));
}).catch((err) => {
console.log("an error while fetching data", err);
})
}
If your data came from an asynchronous request you should use componentDidMount
Invoked once, only on the client (not on the server), immediately
after the initial rendering occurs. At this point in the lifecycle,
you can access any refs to your children (e.g., to access the
underlying DOM representation). The componentDidMount() method of
child components is invoked before that of parent components.
If you want to integrate with other JavaScript frameworks, set timers
using setTimeout or setInterval, or send AJAX requests, perform those
operations in this method.

Resources