How to load new axios data in react component - javascript

I've got a navigation menu that correctly loads components with react-router using <Link to={"/entity"}>Entities</Link>
The components load data using axios and display it in tables. What I'm struggling to accomplish is loading new data when clicking the <link> a subsequent time.
class List extends Component {
constructor() {
super();
this.state = { entities: [], loading: true};
}
componentDidMount() {
this.getData();
console.log('componentDidMount');
console.log(this.state.entities);
}
getData() {
axios.get(this.props.url).then(response => {
this.setState({ entities: response.data, loading: false})
})
}
render() {
...
}
This works well for loading a single set of data. But if I edit a row and open the list again it will have the originally retrieved. Which is the correct behaviour given that code, but how can I reload that? I've tried componentDidUpdate but that creates an infinite loop. I'm assuming due to componentDidUpdate changing the DOM which then again calls componentDidUpdate again.
componentDidUpdate(prevProps) {
this.getData();
console.log('componentDidUpdate');
}
I thought about doing something like adding onClick={this.handleClick} to the menus and changing states or passing values to indicate a new click. But there must be a way to catch an update from router and not just a change of the component.

If you use setState inside componentDidUpdate it updates the component, resulting in a call to componentDidUpdate which subsequently calls setState again resulting in the infinite loop. You should conditionally call setState and ensure that the condition violating the call occurs eventually e.g:
componentDidUpdate(previousProps, previousState) {
if (previousProps.data !== this.props.data) {
this.setState({/*....*/})
}
}

The solution I came up with was to add a datestamp to the link and compare that to the previous timestamp.
<Link to={{"/entity", state: { update: + new Date() } }}>Entities</Link>
componentDidUpdate(prevProps, prevState) {
if (prevProps.update !== this.props.update) {
this.setState({ loading: true })
this.getData();
//console.log('Data Updating - ' + this.props.update);
}
}

Related

Why am I getting "return _this.state.data" instead of JSON object

I have a parent component doing an AJAX call to get a JSON object. I've done a few console.log's to make sure that the data is correct in the parent component, but then when I pass through props, I get a value of:
ƒ data() {
return _this.state.data;
}
What I've done to this point seems simple enough so I can't find what the issue is.
Parent Component:
class InfoBox extends Component {
state = {
data: []
};
componentDidMount = () => {
this.loadDonationsFromServer();
setInterval(this.loadDonationsFromServer, this.props.pollInterval);
};
loadDonationsFromServer = () => {
$.ajax({
url: "https://jsonplaceholder.typicode.com/comments",
dataType: "json",
cache: false,
success: data => {
this.setState({ data });
},
error: (xhr, status, err) => {
console.error(status, err.toString());
}
});
};
render = () => {
return (
<React.Fragment>
<h1>Information</h1>
<InfoList
data={() => this.state.data}
/>
</React.Fragment>
);
};
}
export default DonationBox;
Child Component:
class InfoList extends Component {
constructor(props) {
super(props);
this.state = {
data: this.props.data
};
}
componentDidMount() {
console.log(this.state.data);
//logs: ƒ data() {
// return _this.state.data;
// }
}
render() {
return <div> Placeholder </div>;
}
}
export default InfoList;
I tried using bind in the child component but still got the same thing:
constructor(props) {
super(props);
this.state = {
data: this.props.data
};
this.checkData = this.checkData.bind(this);
}
componentDidMount() {
this.checkData();
}
checkData = () => {
console.log(this.state.data);
};
First, yes, you should change the data prop that you send to InfoList to be this.state.data rather than an anonymous function. So: <InfoList data={this.state.data} />
But, the main issue is in using componentDidMount in the child component, when really you should be using componentWillReceiveProps instead.
componentDidMount is only called once, and it doesn't wait for your AJAX
The componentDidMount lifecycle hook is invoked one time, before the initial render.
In your child component, at componentDidMount you are trying to log this.state.data - but this state is based on what was set in the constructor which was what was passed in as the data prop when you first mounted InfoList. That was [], because InfoBox had yet to receive back data from its Ajax call. To put it another way:
InfoList.componentDidMount() fired before InfoBox.loadDonationsFromServer() got back its response. And InfoList.componentDidMount() does not get fired again.
componentWillReceiveProps is called whenever props change
Instead, your child component should be using the componentWillReceiveProps lifecycle hook. This is invoked every time a component receives new props. Once the parent's state changes (after load donations returns), then it passes new props to the child. In componentWillReceiveProps, the child can take these new props and updates his state.
I have created a code sandbox that shows you through a bunch of log statements what happens when, along with what your props and state look like at various points in the lifecycle. Instead of actually doing an ajax fetch, I'm just doing a 2-second wait to simulate the fetch. In InfoList.js, the code for componentWillReceiveProps is currently commented out; this way, you can see how things work as they stand. Once you remove the comments and start using componentWillReceiveProps, you'll see how they get fixed.
Additional resources
This is a helpful article that pretty much describes the exact same issue you're facing.
An excellent quick reference for React lifecycle hooks is the React Cheat Sheet)
That is because the data prop that is being passed in is a function.
Change
<InfoList data={() => this.state.data} />
to
<InfoList data={this.state.data} />
Just a nit, you don't really need the constructor in your child component to define the state. Just define it the way you have in your parent component.

Managing React component behavior during update

I'm new to ReactJs, and working with the ExtReact framework. I'm displaying a grid, and made a pagination, which is working fine.
I customed the spinner displayed when datas are loading, and it works fine when the "component did mount".
But, when I update the component (like when I switch the page using the ExtReact paginator plugin), there is another native spinner displayed, and I want to custom it too.
My problem is that I don't find a proper way to do it with lifeCycle components methods, since the componentWillUpdate method is deprecated.
I first had only one 'isLoading' state, but I added 'isUpdating' since 'isLoading' was modified after the first render because of the way the store data are loaded.
The isUpdating state seems to stay false, this is what is displayed by the console:
Snapshot
Is updating:false
Updated!
Is updating:false
Saul Goodman!
​
Here is my Component code:
import React, {Component} from 'react';
import {Grid, Column, Toolbar, SearchField} from '#sencha/ext-modern';
import {withRouter} from "react-router-dom";
import Spinner from '../../resources/components/Spinner';
Ext.require('Ext.grid.plugin.PagingToolbar');
class Subscriptions extends Component {
state = {
isLoading: true,
};
store = Ext.create('Ext.data.Store', {
fields: ['Field1', 'Field2', 'Field3'],
autoLoad: false,
pageSize: 30,
proxy: {
type: 'rest', // refers to the alias "proxy.ajax" on Ext.data.proxy.Ajax
url: 'niceadress',
reader: {
type: 'json'
}
}
});
/**
* Loading the datas into the store and then removes the spinner when fetched successfully
*/
componentDidMount() {
this.store.load({
callback: function (records, operation, success) {
this.setState({
isLoading: (!success),
});
console.log("Saul Goodman!");
},
scope: this
});
}
shouldComponentUpdate(nextProps, nextState, nextContext) {
if (nextState.isLoading === true){
return false;
} else {
return true;
}
};
getSnapshotBeforeUpdate(prevProps, prevState) {
this.setState({
isLoading: true,
});
console.log('Snapshot');
console.log('Is loading:' + this.state.isLoading)
return prevState;
}
componentDidUpdate(prevProps, prevState, snapshot) {
this.setState({
isLoading: (!prevState.isUpdating),
});
console.log('Updated!');
console.log('Is loading:' + prevState.isLoading)
}
render() {
if (this.state.isLoading) return <Spinner/>;
return (
<Grid
store={this.store}
plugins={['pagingtoolbar', 'listpaging']}
>
The Grid
</Grid>
)
}
}
export default withRouter(Subscriptions);
Any idea of what I'm doing wrong?
EDIT: Well, I first thought that the store load wasn't going to trigger the componentDidUpdate method, that's why I wrote the isUploading state separately. I'm removing it but I still didn't solved my problem.
How can I do to prevent the virtualDOM to re-render after the setState call in componentDidUpdate?
I'm looking for an elegant way to break this loop.
The isUpdating state seems to stay false, this is what is displayed by
the console
This is because your shouldComponentUpdate is returning false when isUpdating is true.
There is also a typo in your componentDidUpdate's setState
Page changes using ExtReact Paginator strangely aren't triggering the ComponentDidUpdate method. Then, all this doesn't look to be the proper way to change the component behavior. ExtReact documentation looks a bit confusing about this point, I can't see how to override the pagingtoolbar component easily.
Thanks for your answers!

React.js component life cycle, state behavior and asynchronous nature of JavaScript

I have an issue with the expected result and the actual result. Even though the fetchData() and fetchnumberOfCommits() methods are called from the componentWillMount() still the array has no data . But at the end, the render method is called twice, where the array has got data from the API.
I called the setState() method in both the above-mentioned methods where it calls the render method.
My problem is why does the array does not get data as soon as the two methods are called? and at what point array gets data ?
Code example
The render method is called when the component is first mounted and again when your data is received and the state has changed. That's why you're seeing the render method being called twice.
componentWillMount() was deprecated in React 16.3. You should be using componentDidMount() for fetching data and you should expect that your component will render at least once before your data is fetched, so you'll need to render either null or a loading state before the data is fetched. I provided an example of how you can check if it was loaded correctly and show a loading message.
class App extends React.Component {
constructor() {
super();
this.state = {
check: true,
repositories: [],
commits: [],
};
}
componentDidMount() {
this.fetchData();
this.fetchNumberOfCommits();
}
fetchData() { /*...*/ }
fetchNumberOfCommits() { /*...*/ }
isLoaded() {
return this.state.respositories.length > 0;
}
render() {
const { repositories } = this.state;
if(isLoaded) {
return repositories.map(repo => {
return (
<Api
repo={repo.name}
createdDay={repo.createdDay}
/>
);
});
}
return <h1>Loading repos...</h1>;
}
}
As stated above you should remove it from componentWillMount as that has been deprecated as of 16.3. Should be able to drop it into a componentDidMount and it will work for you.
I changed from componentWillMount() to componentDidMount() as well, but I got the same problem . Reason for that was the asynchronous nature of JavaScript . When you use the promises it doesn't wait until you get the results from the API call. It just run the codes down the order keeping a promise unitl it get data. That is the reason that I got an empty array even though the function was called.
You can use async/await to make the code synchronized and then it will wait until you get the results from the API call.
If you run the following code example you can see the results in the console where fetchData1() gives an empty array and the fetchData2() to gives the array with data .Further if you examine the console well, you will see that when the setState() function is called the render() method triggers.
import React, { Component } from 'react';
class App extends Component {
constructor(){
console.log('This is from constructor');
super();
this.state={
repositories:[],
}
}
componentDidMount(){
console.log('This is from componentDidMount');
this.fetchData1();
this.fetchData2();
}
fetchData1(){
console.log('This is function is using promises');
fetch('https://api.github.com/users/94ju/repos').then(results => results.json()).then(repositories =>this.setState({
repositories
})).then(
console.log( this.state.repositories),
console.log(this.state)
)
console.log('End of using promises')
}
async fetchData2(){
console.log('This is function is using async-await');
const check =await fetch('https://api.github.com/users/94ju/repos');
const checkjson =await check.json();
console.log('Before setState');
this.setState({ async_repositories: checkjson });
console.log( this.state.async_repositories);
console.log(this.state);
console.log('End of async-await');
}
render() {
console.log("Starting render function");
const repo =this.state;
console.log(repo);
console.log('Ending render function');
return (
<div>
</div>
);
}
}
export default App;

React and Reloading API Call

I'm working on a project in React that pulls random data from an API and processes it. Currently, on loading the page, that component that actually pulls the API doesn't load by design.
So to keep things simple, I have two Components. I load Component1 and using a state showDiv: false I don't load Component2 with the API call. I have a button that when clicked changes the state to true, and by doing that, loads Component2 inside Component1.
Now what I want to do is have Component2 hidden again, and then brought back, and call a new set of data from the API and process it. I wrongly assumed that once Component2 was pulled off the page, that bringing it back would load it from scratch eg. rerun the componentWillMount() function inside Component2, which is where my API call is located. Now that I see that's not the case, I'm not sure how to accomplish that without reloading the page entirely.
EDIT:
Here's the component to be rendered:
(This has been heavily simplified. The API pulls an array, and that array is processed into an array of objects, and then it is placed in the state. The component's render is then populated by content taken from that processed array from the state)
class Questions extends Component {
constructor(props) {
super(props);
this.state = {
isQuestionLoaded: false,
questions: [],
};
}
componentWillMount() {
fetch(this.props.category)
.then(results => {
return results.json();
}).then(data => {
let questions = data.results.map((question) => {
return question;
})
this.setState({questions: questions, isQuestionLoaded: true});
})
}
Here's the App.js render of this component:
render() {
let questionRender = this.state.showQuestions ?
<Questions
category={ this.state.categoryActive }
questionNumber={ this.state.questionNumber }
onClickCorrect={ this.nextQuestionCorrect }
onClickCorrectLast={ this.lastQuestionCorrect }
onClickIncorrect={ this.nextQuestionIncorrect }
onClickIncorrectLast={ this.lastQuestionIncorrect }
score={ this.state.score }
correct={ this.state.correct }
answerCorrect={ this.state.answerCorrect } />
: null;
Here's the function that takes the component away:
lastQuestionCorrect() {
this.setState({
showQuestions: false,
showFinal: true
});
}
Here's the function that brings the component back:
onClickReplay() {
this.setState({
showQuestions: true,
showFinal: false,
});
}
A new array was being loaded, the problem was one of my states in App.js was not being reset correctly by onClickReplay()

React router + redux navigating back doesn't call componentWillMount

Currently I pre-load data from api in container component's lifecycle method componentWillMount:
componentWillMount() {
const { dept, course } = this.props.routeParams;
this.props.fetchTimetable(dept, course);
}
It is called when user navigates to route /:dept/:course, and it works fine, until you navigate from let's say: /mif/31 to /mif/33 and then press back button. The component is not actually reinitialized, so the lifecycle method is not called, and the data isn't reloaded.
Is there some sort of way to reload data in this case? Should I maybe use another method of preloading data? I see react router emits LOCATION_CHANGE event on any location change, including navigating back, so maybe I can somehow use that?
If it matters, here's is how I implement data loading:
import { getTimetable } from '../api/timetable';
export const REQUEST_TIMETABLE = 'REQUEST_TIMETABLE';
export const RECEIVE_TIMETABLE = 'RECEIVE_TIMETABLE';
const requestTimetable = () => ({ type: REQUEST_TIMETABLE, loading: true });
const receiveTimetable = (timetable) => ({ type: RECEIVE_TIMETABLE, loading: false, timetable });
export function fetchTimetable(departmentId, courseId) {
return dispatch => {
dispatch(requestTimetable());
getTimetable(departmentId, courseId)
.then(timetable => dispatch(receiveTimetable(timetable)))
.catch(console.log);
};
}
You need to use componentWillReceiveProps to check if new props (nextProps) are same as existing props (this.props). Here's relevant code in Redux example: https://github.com/reactjs/redux/blob/e5e608eb87f84d4c6ec22b3b4e59338d234904d5/examples/async/src/containers/App.js#L13-L18
componentWillReceiveProps(nextProps) {
if (nextProps.dept !== this.props.dept || nextProps.course !== this.props.course) {
dispatch(fetchTimetable(nextProps.dept, nextProps.course))
}
}
I might be wrong here, but I believe the function you are looking for is not componentWillMount but componentWillReceiveProps,
assuming you are passing down variables (like :courseId) from redux router to your component, using setState in componentWillReceiveProps should repaint your component.
Otherwise, you can subscribe to changes in your store: http://redux.js.org/docs/api/Store.html
Disclaimer: I probably know less about redux then you.

Categories

Resources