Is this React and Axios then promise usage correct? - reactjs

I am using axios for a React project, and I was wondering if the usage of then promise is correct in this case.
Basically, I use axios to fetch data from the database when the component renders.
class Participants extends React.Component{
constructor(props){
super(props);
this.state = {
databaseUsers: [],
}
this.getUsers = this.getUsers.bind(this);
}
getUsers(){
var users = axios.get('/users/get-users').then((response) => {
this.setState({databaseUsers: response.data});
});
}
componentWillMount(){
this.getUsers();
}
render(){
console.log(this.state.databaseUsers);
return(** html tree **);
}
}
What I observe is that the state of the component is set twice, once when the rendering occurs, and the then promise fires, and a second time when the promise is done fetching the data from the database and sets the state.
How do I get more control over this? Like actually wait for the data on the database, and then render?
Any tips are welcome.

There are other ways to implement what you did with several components.
But let's stick to this example.
There is nothing wrong to rendering twice, as you don't want to wait for the response and then display output.
You can have a loading flag so you could show a "loading" code and when loaded show the output.
Or you can have 1 parent component that manages the work:
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
loading: true,
data: []
}
}
componentDidMount() {
this.setState({loading: true})
axios.get('/users/get-users').then((response) => {
this.setState({
loading: false,
data: response.data
})
});
}
render() {
if (this.state.loading) {
return <LoadingComponent />;
}
return <DataComponent data={this.state.data} />
}
}

Related

How to update React component?

I have a child object (element of list) which is rendered inside(?) the parent one. The component has the following properties (from JSON):
contract
{
id,
name,
}
But I need to add another one additional property which is filled in after an HTTP request with an external function to the API (for example, uuid) using one of the existing properties of an object.
My current React code looks the following way (only child component is provided):
class Contract extends Component {
constructor(props){
super(props);
this.state = {data: this.props.contract};
getUuidByName(this.state.data.name).then(val => {
this.state.data.uuid = val;
});
}
componentDidUpdate(){ }
render() {
return <tr>
<td>{this.state.data.id}</td>
<td>{this.state.data.name}</td>
<td>{this.state.data.uuid}</td>
</tr>
}
}
Everything rendered good except an additional property: uuid. Of course I do something wrong or don't do some important thing, but I have no idea what to do.
You are mutating state in the constructor. Never mutate state directly. If you are needing to set/initialize some state after it's been constructed, or mounted, then you should use the componentDidMount lifecycle method. Ensure you enqueue the state update via the this.setState method.
class Contract extends Component {
constructor(props){
super(props);
this.state = {
data: props.contract,
};
}
componentDidMount() {
getUuidByName(this.state.data.name).then(val => {
this.setState(prevState => ({
data: {
...prevState.data,
uuid: val,
},
}));
});
}
componentDidUpdate(){ }
render() {
return (
<tr>
<td>{this.state.data.id}</td>
<td>{this.state.data.name}</td>
<td>{this.state.data.uuid}</td>
</tr>
);
}
}
Do not modify state directly.
Because you're directly modifying the state, React isn't triggering a re-render.
Try the following in your constructor instead:
constructor(props){
super(props);
this.state = {data: this.props.contract};
getUuidByName(this.state.data.name).then(val => {
this.setState({
data: {
...this.state.data,
uuid: val
}
});
});
}

Can we send the JSON obtained from an API as a child component, instead of each individual attribute of the object?

I have been trying to send the data obtained by an API call to a child component via state and it doesn't seem to work.
I have been sending each individual attribute of the object as a prop to the child component.
Is there a way to send the whole JSON response as a prop to a child component?
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
data: {},
name: ""
};
}
componentWillMount() {
this.getWeather();
}
getWeather(city) {
fetch(
`https://api.apixu.com/v1/current.json?key=2da827a3ce074ddb85417374xxxxxx&q=paris`
)
.then(res => res.json())
.then(data => {
this.getData(data);
})
.catch(err => {
return Promise.reject();
});
}
getData(data) {
var location = data.location.name;
this.setState({ data: data, name: location });
console.log(this.state.name);
console.log(this.state.data);
}
render() {
return <Child name={this.state.name} data={this.state.data} />;
}
}
class Child extends React.Component {
render() {
var data = this.props.data;
return (
<div>
<h1>{this.props.name}</h1>
<h1> {data.current.cloud}</h1>
</div>
);
}
}
ReactDOM.render(<Parent />, document.getElementById("root"));
I expect the data object to also be passed to the child but it doesn't and I get a crash screen stating that the data object is undefined.
Is there a way to send the whole JSON obtained in an API call as a prop to a child component?
Your Child component will render before the getWeather api return the data. So this.props.data in Child component will be {}, app crash when you access data.current.cloud.
You need to check whether data is not empty and it has current property. So your code should be
class Child extends React.Component {
render() {
var data = this.props.data;
return (
<div>
<h1>{this.props.name}</h1>
<h1>{data && data.current ? data.current.cloud : ''}</h1>
</div>
);
}
}
It is always a best practice to do all API calls in method "ComponentDidMount" rather than "ComponentWillMount". This will do away with your checking that whether response came from API or not. Once the response comes, component will be re-rendered. So, you can do like below
componentDidMount() {
this.getWeather();
}
As an addition to #tien Duoung's comment,
You may want to add an extra state variable. You could call it fetching or loading. The purpose will be to at least display something while your api result is not ready. It could be like so:
this.state = {
data: {},
name: "",
fetching: true
}
In the .then of your getData method, once data.current is available, this.setState({ fetching: false })
getData(data) {
var location = data.location.name;
this.setState({ data: data, name: location, fetching: false });
console.log(this.state.name);
console.log(this.state.data);
}
Then pass fetching as a prop to the child component too, and when fetching is true, render a loader component or say a placeholder <h1>Loading...</h1>

Accessing React state variables when they are loaded form promise

I have a simple component that loads in the data from a job as follows
export class ViewJob extends Component {
constructor() {
super();
this.state = {
currentJob: {},
checkedCompleted: false,
};
}
componentDidMount() {
loadJobFromId(this.props.id)
.then(job => this.setState({currentJob: job}))
}
In my render when I try to access a nested property:
this.state.currentJob.selectedCompany
I get an error:
Cannot read property 'root' of undefined
This seems to be because the state of selectedCompany is first undefined and then when the promises resolves it is set.
What is the best practice for handling this in React?
You should render the part that's using this.state.currentJob.selectedCompany only once the promise is resolved. So you can try something like this:
export class ViewJob extends Component {
constructor() {
super();
this.state = {
currentJob: {},
checkedCompleted: false,
jobsFetched: false
};
}
componentDidMount() {
loadJobFromId(this.props.id)
.then(job => this.setState({currentJob: job, jobsFecthed: ture}))
}
render(){
{ this.state.jobsFetched ? **[RENDER_WHAT_YOU_ARE_RENDERING_NOW]** : <Text>Loading...</Text> }
This way the component will be rendered only when the jobs are fetched/ promise resolved.

Stop rendering of a component after componentDidMount()

I have a search page with three components. The browse topics component lists the topics to choose from. The browse articles component lists all the articles based on the topic ID and loads all articles if there is no topic id. The home component holds the browsetopics and browsearticles component, and changes its state according to the topic which is clicked.
class BrowseTopics extends React.Component {
constructor(props) {
super(props);
this.topicSelect = this.topicSelect.bind(this);
this.state = {error: "", topics: []};
}
componentDidMount(){
// API call which updates state topics with the list of topics
}
topicSelect(id,e) {
e.preventDefault();
this.props.topicChange(id);
}
render () {
// Rendering list of topics from API and nothing if request has not been sent
}
}
class BrowseArticles extends React.Component {
constructor(props) {
super(props);
this.state = {error: "", articles: [], url: "/api/articles"};
}
componentDidMount() {
if(this.props.topicId){
var url = '/api/topic/'+this.props.topicId+'/articles';
this.setState({url: url});
}
// Make a request to url and get articles
}
render () {
// Renders the list of articles
}
}
class Home extends React.Component {
constructor(props) {
super(props);
this.handleUpdate = this.handleUpdate.bind(this);
this.state = {topicId: ""};
}
handleUpdate(topicId) {
this.setState({topicId: topicId});
}
render () {
return(
<div>
<BrowseTopics user={this.props.user} topicChange={this.handleUpdate}/>
<BrowseArticles user={this.props.user} topicId={this.state.topicId}/>
</div>
);
}
}
What I need is, I want the browseTopics component to stop re-rendering on parent state change.
I tried using shouldComponentUpdate() (which returns false) but that even stops the componentDidMount() part and the list isn't populated.
Once the request to API is made and component is rendered, I want all further re-rendering of browseTopics to stop for the sorting to function properly.
From docs:
if shouldComponentUpdate() returns false, then componentWillUpdate(), render(), and componentDidUpdate() will not be invoked
I'd probably want to set some sort of flag telling my BrowseTopics component that the API request has been made and I no longer need/want the component to update:
class BrowseTopics extends React.Component {
constructor(props) {
super(props);
this.topicSelect = this.topicSelect.bind(this);
this.state = {
error: "",
topics: [],
hasFetched: false // flag for API
};
}
componentDidMount(){
// API call which updates state topics with the list of topics
fetch( 'myapi.json' )
.then( res => {
// set flag denoting API results have been fetcehd
this.setState({
hasFetched: true,
topics: <your topics>
});
})
}
shouldComponentUpdate(nextProps, nextState) {
if ( this.state.hasFetched ) {
return false;
}
return true;
}
...

Access Other component variable/states

I want to access state data or Portfolio in TotalTab/ProfitTab/LossesTab. I want them to get the data updated ( get data bind ? ) when async fetchData is finish also.
My code is as below
class Portfolio extends Component{
constructor(props) {
super(props);
this.state = {
isLoading: true,
dataSource : ""
};
}
componentDidMount() { //This function get called once when react finished loading. Something like jQueryDom Ready seems to be https://facebook.github.io/react-native/docs/tutorial.html
this.fetchData();
}
fetchData() {
fetch("http://beta.setmonitor.com/api/trading/summary?portfolio_id=3")
.then((response) => response.json())
.then((responseData) => {
this.setState({
dataSource: responseData,
isLoading: false
});
})
.done();
}
render() {
return (
<ScrollableTabView>
<TotalTab tabLabel="Total" />
<ProfitTab tabLabel="Profit" />
<LossesTab tabLabel="Losses" />
</ScrollableTabView>
);
}
};
I tried creating an another class for just fetching data and storing and use it like new ClassName but the problem is that async data don't get update in the view.
Utilize props. You can pass the data down to the Tab components and React will automatically keep it in sync. If needed, you can also declare a function in the Portfolio component and pass that down to the children as needed.
React Docs - Data Flow

Resources