componentWillMount does not finish before render - reactjs

I am facing some difficulties regarding to state in reactjs.
As far as I know componentWillMount is a place to load the data with ajax call before the components get rendered.
I have a simple simple project which populates stack of panels with a loaded data and show it on board. However the data from ajax call do not get set before rendering of the component and this leads to rendering of the board with an empty array. The follwoing is my complete source:
import React from "react";
export class Panel extends React.Component{
render() {
return (
<div>
<div className="row panel">
<div className="col-sm-12 header">sfsfsf</div>
<div className="col-sm-12 body">fsdfsfs</div>
<div className="col-sm-12 footer">fasfaf</div>
</div>
</div>
);
}
}
and Board class which is a root of the problem is as follows :
import React from "react";
import {Panel} from "./Panel";
export class Board extends React.Component{
constructor(props){
super();
this.state={news: []};
}
componentWillMount(){
this.state={news: []};
$.ajax({
url: "http://localhost:3003/json.txt",
dataType: 'json',
cache: false,
success: function(data) {
var arr=[];
for (var key in data) {
arr.push(data[key]);
console.log(data[key]);
}
this.state={news: arr};
}});
}
render() {
return (
<div>
{
this.state.news.map((item,i)=> <Panel key="i"/>)
}
</div>
);
}
}
Also the last class is index.js:
import React from "react";
import {render} from "react-dom";
import {Board} from "./component/Board";
class App extends React.Component{
render(){
return(
<div>
<Board/>
</div>
);
}
}
render(<App/>, document.getElementById('middle'));
So as you can see in the Board.js class I initialize my array in render function and then I use componentWillMount to fill the news array which I expect it to happen after componentWillMount is finished but in my case the array is empty when rendering happens. Any idea?
*********UPDATE***************
I also tried it with componentDidMount but it did not work and the same problem

componentWillMount() is finishing before render but because ajax is async it will not execute until the request completes.
You should not set state using this.state = .... Instead use:
this.setState({news: arr});
This will set the value and trigger the component and all children to render. Just write your render function to handle null data nicely and you'll have your expected result without blocking JS execution.
As suggested here it is with proper binding:
import React from "react";
import {Panel} from "./Panel";
export class Board extends React.Component{
constructor(props){
super();
}
getInitialState() {
return {news: []};
}
componentWillMount(){
$.ajax({
url: "http://localhost:3003/json.txt",
dataType: 'json',
cache: false,
success: (data) => {
var arr=[];
for (var key in data) {
arr.push(data[key]);
console.log(data[key]);
}
this.setState({news: arr});
}
});
}
render() {
return (
<div>
{
this.state.news.map((item,i)=> <Panel key="i"/>)
}
</div>
);
}
}
The arrow function handles the binding. Simmilar to function(){}.bind(this)

You are doing a ajax call which is async... By definition it will continue to execute the code without waiting for the ajax's response.
you can turn that synchronous by setting async: false on the $.ajax options.

Related

React : how to properly use componentDidMount to make an api call

My react app doesnt show my api calls. At least some of them.
I would like to make sure my code does wait for the api call to resolve.
I saw a tutorial here https://codewithnico.com/react-wait-axios-to-render/ where they use a functional component with if isLoading to show loading status and useEffect to make the api call and wait for the results.
I have a class component and I would like to check if the use of componentDidMount is indeed waiting for the api call to resolve like useEffect is in the functional tutorial i saw
Here is the code:;
import React from "react";
import axios from "axios";
import { StreamField } from "./StreamField/StreamField";
class PostDetail extends React.Component {
constructor(props) {
super(props); this.state = {
post: [],
loading: true, };
}
componentDidMount() {
const pk = this.props.match.params.id;
axios.get(`/api/cms/pages/${pk}/`).then((res) => {
const post = res.data;
this.setState({
post,
loading: false });
}) }
render() {
if (!this.state.loading) {
const post = this.state.post;
return (
<div className="col-md-8">
<img src={post.header_image_url.url} className="img-fluid rounded" alt=""/>
<hr />
<h1>{post.title}</h1>
<hr />
<StreamField value={post.body} />
</div> );
}
else {
return <div className="col-md-8">Loading...</div>;
}
}
}
export { PostDetail };
What do you think? Is my code in cause for my component not loading properly ( the delay to resolve is quite long on my test machine : several seconds)

componentDidMount function not working properly

So I'm creating a simple React app that fetches data from a JSON API server and displays it in a table. Here is the code for the main component:
import React, {Component} from "react"
import Table from "./Table.js"
class Main extends Component{
constructor(props) {
super(props);
this.state = {
array: null
}
}
render(){
return(
<div>
<Table array={this.state.array} />
</div>
)
}
componentDidMount() {
console.log("Checking");
fetch("https://api.myjson.com/bins/1exld0")
.then(response => response.json())
.then(data => {
console.log(data);
this.setState({ array: data })
});
}
}
export default Main;
However when I run the app the table is not being rendered. This has something to do with the componentDidMount function not working properly because I have a line of code:
console.log("Checking")
and the console doesn't log anything.
Can anyone help?
Edit: Here are the Table and TableRow components:
import React, {Component} from "react"
import TableRow from "./TableRow.js";
class Table extends Component{
constructor(props){
super(props);
this.state={
}
}
render(){
var self=this;
var items=this.props.array.map(function(item, index){
return <TableRow obj={item} key={index}/>
})
return(
<table>
{items}
</table>
)
}
}
export default Table
/
import React, {Component} from "react"
class TableRow extends Component{
constructor(props){
super(props);
this.state={
}
}
render(){
return(
<tr><td>{this.props.obj.color}</td><td>{this.props.obj.value}</td></tr>
)
}
}
export default TableRow
And here is the error that I am being given when I run the app in the browser:
TypeError: Cannot read property 'map' of null:
var items=this.props.array.map(function(item, index){
Set the array to the empty array instead of null in the Main constructor. There will be an initial render cycle before the async data is loaded, and that crashes since you cannot map over a null array.
move componentDidMount() out of the render() method , that will fix it

componentWillMount fails to set the state

I have a parent component called Home.js which retrieves the data from an API and send the data as a property to it's child component DishItem.js that shows the dish details. I am trying to render the dish_name, quantity (that can be changed) and net_cost, and I am storing all of these in a state and setting their state for the initial render through componentWillMount() lifecycle method. But it fails to set the state and returns undefined.
Home.js
import React, { Component } from 'react';
import DishItem from './DishItem';
import $ from 'jquery';
export default class Home extends Component {
constructor() {
super();
this.state = {
dish: ''
}
}
getDishes() {
$.ajax({
url: 'http://testbed2.riktamtech.com/foody_buddy/public/api/dishes/dish/31816',
dataType: 'json',
cache: false,
success: function(response) {
this.setState({dish: response.data});
}.bind(this),
error: function(xhr, status, err) {
console.log(err);
}
});
}
componentWillMount() {
this.getDishes();
}
componentDidMount() {
this.getDishes();
}
render() {
return (
<div className="Home">
<DishItem dish={this.state.dish}/>
</div>
);
}
}
DishItem.js
import React, { Component } from 'react';
import Quantity from './Quantity';
import { browserHistory } from 'react-router';
import './style.css';
export default class DishItem extends Component {
constructor(props) {
super(props);
this.state = {
quantity: 1,
delivery: '',
total_cost: '',
net_cost: null,
counter: 1
}
}
componentWillMount() {
console.log(this.props.dish.net_cost); //undefined instead of some value
this.setState({net_cost: this.props.dish.net_cost});
console.log(this.state.net_cost); // null
}
handleChangeInQuantity(value) {
var net_cost = 0;
var total_cost = 0;
total_cost = value * this.props.dish.cost;
net_cost = value * this.props.dish.net_cost;
this.setState({quantity: value, net_cost: net_cost, total_cost: total_cost});
}
saveAndContinue(e) {
e.preventDefault();
}
render() {
let address = <div>{this.props.dish.address}<br/>{this.props.dish.landmark}<br/>{this.props.dish.locality}</div>
return (
<div>
<h3>{this.props.dish.name}<small>{this.props.dish.description}</small></h3>
Total- {this.state.net_cost}
<Quantity change_in_quantity={this.handleChangeInQuantity.bind(this)} />
<button className="btn btn-default" onClick={this.saveAndContinue.bind(this)}>Next</button>
</div>
);
}
}
componentWillMount() function is triggered only once, it does not wait to get the data from the server. you should use componentWillReceiveProps() in the DishItem component, and set the new state with the data sent from the Home component to it. This way whenever the DishItem receives new props from the Home component, it will update accordingly.
You should not use the componentWillMount method of a React component to do asynchronous data fetching. Remove your call to the getDishes method and leave it in componentDidMount. In the render method of your Home component, check if the state already has the dish data in the state. If it doesn't, don't render the DishItem component. You can put a "Please wait, loading" message or something like that instead.
When the page is loaded, React will render the component with the "loading " message and start loading the dish data asynchronously. When the data is done loading and the state is set, React will render the component again with the DishItem component.
Here's an updated version of your Home component:
import React, { Component } from 'react';
import DishItem from './DishItem';
import $ from 'jquery';
export default class Home extends Component {
constructor() {
super();
this.state = {
dish: null
};
}
getDishes() {
$.ajax({
url: 'http://testbed2.riktamtech.com/foody_buddy/public/api/dishes/dish/31816',
dataType: 'json',
cache: false,
success: function (response) {
this.setState({ dish: response.data });
}.bind(this),
error(xhr, status, err) {
console.log(err);
}
});
}
componentDidMount() {
this.getDishes();
}
render() {
return (
<div className="Home">
{
this.state.dish === null ?
<div>Please wait, loading…</div> :
<DishItem dish={this.state.dish} />
}
</div>
);
}
}
it seems that I can't add a comment so I will just send this since there are already many answers. Please don't give me minus one because of this :)
Even though it is in componentDidMount, once it fetches the data, it will re-render the DOM so it will look like it is the initial render. If the fetching is slow, you will see it blank for a moment and then renders to what you need it to be.
If you really really need to make it show up at the very first moment, I suggest that you fetch the data and save it inside Flux store or Redux store before redirecting to this page. For example, fetch the data and do the redirection inside then() or callback function.

How to call other library method from Reactjs

I'm building a Reactjs component for SharePoint 2013 page. In the SharePoint JSOM, I can get the current context by calling GetCurrentCtx(). I want to call this method in my ReactJs component but I don't how to do:
import React from 'react';
class TestComponent extends React.Component {
constructor(props){
super(props);
this.state = {likesCount:0, listTitle:''};
this.onLike = this.onLink.bind(this);
}
onLink(e){
e.preventDefault();
var ctx = GetCurrentCtx(); //<- this line doesn't work.
this.setState({listTitle:ctx.ListTitle});
return false;
}
render(){
return (
<div>
<div>List Title: {this.state.listTitle}</div>
<div><button onClick={this.onClick}>Get List Title</button></div>
</div>
);
}
}
export default TestComponent;
Any idea?
something like this:
import { GetCurrentCtx } from './filepath_for_this_function_export'
Just import file/component where is your method

Data not displaying when doing an ajax request in React

So I'm trying to mock an ajax call using a React's Container(purely to fetch the data and pass it along to its children), but I'm not getting anything.
Instead get the following error: TypeError: Cannot read property 'map' of undefined. Which basically tells me that the users is either empty or not yet defined, right?
Right now I'm following the following structure UserListContainer (fetches the data) => UserList Component (displays the data as a prop).
UserList Container
// Container responsible only to fetch User data
import React from 'react';
import UserList from '../../ui/components/users/userList.jsx';
export default class UserListContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
users: []
}
}
componentDidMount() {
$.ajax({
// url: "../../users/users.json",
url: "https://gist.githubusercontent.com/dantesolis/267a298f3d6ac524bc2a7d80960a16b5/raw/7929bb23f1757b85adcead4eed3023cd3c7453df/users.json",
dataType: "json",
success: function(users) {
this.setState({users: users});
}.bind(this)
});
}
render() {
// const usr = this.props.user;
// let usrs = users_mockup ? users_mockup : this.props.users;
return (
<UserList users={this.state.users} />
);
}
}
UserList Component
import React from 'react';
import User from '../users/user.jsx';
// import { Link } from 'react';
export default class UserList extends React.Component {
constructor(props) {
super(props);
}
renderUser({name, _id}) {
return <li>{_id}-{name}</li>
}
render() {
return (
<div className="main">
<ul>{this.props.users.map(renderUser)}</ul>
</div>
);
}
}
That is because users is undefined in the success callback. This can happen because the URL is not working correctly. After taking a look at it, it seems it returns javascript and not json. You should try removing the comment in the first line to make it json compliant.

Resources