Passing state to async component loader for callback and rendering - reactjs

I have a set of Reacts components that load asynchronously to deal with a large dataset. I have an initial loader working through react-spinners BeatLoader, which works fine. My problem arises when I try to replicate this effect with a Loading inventory... message when trying to pass in the productsLoading state in its parent component as I did for the warehouse loading state at the index.jsx level.
I'm only trying to get a callback working in the child component for this.
index.jsx:
import React from 'react'
import { withRouter } from 'react-router-dom'
import WarehouseDetails from './warehouse-details'
import { BeatLoader } from 'react-spinners';
;
class WarehouseReport extends React.Component {
state = {
warehouse: {
name: '',
sales: '',
cost: ''
},
categories: [],
products: [],
warehouseLoading: true, <<< top level loader
categoriesLoading: true,
productsLoading: true, <<< component level
}
componentDidMount() {
this.loadWarehouseInfo()
}
loadWarehouseInfo = () => {
return fetch(`//${window.location.host}/${this.props.match.params.account_id}/warehouse_info/${this.props.match.params.warehouse_id}`)
.then(response => response.json())
.then(json => {
this.setState({
warehouse: {
name: json.warehouse_name,
sales: json.warehouse_sale_total,
cost: json.warehouse_cost_total
},
warehouseLoading: false
}, this.loadCategoryInfo)
})
}
loadCategoryInfo = () => {
return fetch(`//${window.location.host}/${this.props.match.params.account_id}/warehouse_info/${this.props.match.params.warehouse_id}/categories`)
.then(response => response.json())
.then(json => {
this.setState({
categories: json,
categoriesLoading: false
}, this.loadProductInfo)
})
}
loadProductInfo = () => {
return fetch(`//${window.location.host}/${this.props.match.params.account_id}/warehouse_info/${this.props.match.params.warehouse_id}/category_products/${this.state.categories.map(category => category.id).join(',')}`)
.then(response => response.json())
.then(json => {
this.setState({
products: json,
productsLoading: false, <<<< setup states
loading: false
})
})
}
render() {
const { warehouse, categories, products, warehouseLoading } = this.state
return <div>
{ warehouseLoading ? <BeatLoader
color={'#4db3bf'}
/> : <WarehouseDetails warehouse={warehouse} categories={categories} products={products} /> }
</div>
}
}
export default withRouter(WarehouseReport)
When I drill down to the component I'm trying to set up the loader for, I have this:
category-details.jsx:
import React from 'react'
import ProductDetails from './product-details'
import NumberFormat from 'react-number-format';
export default function CategoryDetails({ name, count, products, productsLoading }) {
<<< state is passed down; passing state outside the curly brackets only replaces the following component with the loading statement/item)
return <div>
<div><h3>{name}</h3></div>
<div>{count} products found</div>
<h4>Products</h4>
<table className="table">
<thead>
<tr>
<th>Name</th>
<th>Rental $</th>
<th>Sale $</th>
</tr>
</thead>
<tbody>
{ this.state.productsLoading ? 'Loading inventory...' : products.map(product => <ProductDetails
{...product}
/>) <<<< component-level loader here
}
</tbody>
</table>
</div>
}
What's the correct way of getting this loader in place? Thanks for your time, I'm still getting a handle on states in React.

I think something you're looking for is:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all

Related

How I do use fetch API and store response in the state?

I have to get a file from the server, After the component is rendered, that contains information from cities, and I must assign it to "citiesData" in the state. But the data is not received because it is not seen in the output.
what is the issue with my fetch?
server file:
IranMap(the file seems to have a problem in fetch):
import React from 'react';
import './IranMap.css';
import CityModal from './CityModal';
class IranMap extends React.Component {
state = {
error: null,
citiesData: null,
selectedCity: null,
isModalOpen: false,
};
componentDidMount() {
fetch('http://localhost:9000/cities')
.then(response => response.json())
.then((result) => {
this.setState({
citiesData: result
});
},
(error) => {
this.setState({
error
});
}
)
}
cityClicked = (id) => (event) => {
event.preventDefault();
fetch(`http://localhost:9000/cities/${id}`,{method: 'GET'})
.then(res => res.json())
.then(result => {
this.setState({
selectedCity: result,
isModalOpen: true
});
})
}
closeModal = () => {
this.setState({
isModalOpen: false,
});
};
render() {
if(this.state.error){
return <div>Error: {this.state.error.message}</div>;
}
else {
return (
<div>
<div className="map-container">
{(this.state.citiesData || []).map((record) => (
<div
key={record.id}
className="city-name"
style={{
top: `${record.top}%`,
left: `${record.left}%`,
}}
onClick={this.cityClicked(record.id)}
>
{record.name}
</div>
))}
</div>
<CityModal
city={this.state.selectedCity}
isOpen={this.state.isModalOpen}
onClose={this.closeModal}
/>
</div>
);
}
}
}
export default IranMap;
This is my output. it should be show cities name. but this is empty:
I think what you are trying to do is render the entire object,u cant do that, try the render each element, The second part of my answer is that you should use an asynchronous task.
I hope my answer guided you

React - delete button deletes correct entry but visually removes bottom row

From reading previous SO posts and blogs I'm not sure if this is related to props. Either way I'm baffled.
I have a class component, responsible for loading data, which uses a functional component for displaying the data. When the delete button, in the functional component, is pressed it calls props.onDelete which does a fetch and reloads the data. The correct row is deleted from the DB but in the browser it's always the bottom row which is removed. On reloading the page the correct data is displayed.
I've put a breakpoint in the functional component and in the class component render and loadStations methods. On clicking delete button I can see that loadStations is called (which calls setState) and then the functional component is called. However, the render method is never called.
Stations.js (the class component parent)
import React, {Component} from "react";
import EditableTable from "../util/EditableTable";
// column definitions
const columns = [
...
]
export default class Stations extends Component {
constructor() {
super();
this.state = {
stations: []
};
}
componentDidMount () {
this.loadStations();
}
loadStations() {
fetch(`/api/stations`)
.then(response => response.json())
.then(response => {
this.setState({
stations: response.data
})
});
}
saveStation = (e, station) => {
...
}
deleteStation = (e, dataRowIdx) => {
e.preventDefault();
var stationId = this.state.stations[dataRowIdx].stationId;
fetch(`/api/station/${stationId}`, {
method: "DELETE"
})
.then(response => response.json())
.then(data => {
if (data.error) {
this.setState({ error: data.error });
} else {
this.loadStations();
}
}).catch(error => {
this.setState({
error: error.message
});
});
}
render() {
return (
<div>
<h4>Stations</h4>
<EditableTable
columns={columns}
data={this.state.stations}
onDelete={this.deleteStation}
onChanged={this.saveStation}
></EditableTable>
</div>
);
}
}
EditableTable.js (the functional component)
import React, { useState } from "react";
import EditableLabel from "./EditableLabel";
export default function Table(props) {
var emptyDataRow = {};
props.columns.forEach( (column) => {
emptyDataRow[column.property] = ""
});
const [newRowState, setNewRowState] = useState(emptyDataRow);
function cellChanged(e, value, dataRowIdx, columnProperty) {
var dataRow = props.data[dataRowIdx];
dataRow[columnProperty] = value;
props.onChanged(e, dataRow);
}
return <table>
<thead>
<tr>
{props.columns.map( (column, idx) =>
<th key={idx} value>{column.label}</th>
)}
<th></th>
</tr>
</thead>
<tbody>
{props.data.map( (dataRow, dataRowIndex) =>
<tr key={dataRowIndex}>
{props.columns.map( (column, columnIndex) =>
<td key={columnIndex}>
<EditableLabel
value={dataRow[column.property]}
column={column}
onChanged={(e, newValue) => { cellChanged(e, newValue, dataRowIndex, column.property); }}
></EditableLabel>
</td>
)}
<td><button onClick={(e) => { props.onDelete(e, dataRowIndex); }}>delete</button></td>
</tr>
)}
</tbody>
</table>
}
This is most likely because you are using the index given by the map method as the key.
This just uses the item's location in the array as the key, which will not be a sufficient identifier if something in the middle of the array has been removed.
You should give a key that is identifiably unique for that item in the array, eg an unchanging ID or name.

ComponentDidMount doesn't see parent's props

I'm new to react and I'm stuck again. I'm trying to map my array to create new array of objects inside of my child component. Here's my issue - my method componentDidMount gets executed before data came from parents props, and my state stays empty. When I'm console.loging this.props and the end of componentDidMount I receive empty array, but when I'm console.loging it on render method it gives me 4 empty arrays, then it fills in to expected 300. What I'm doing wrong?
Parent component:
import "./App.css";
import { CompanyList } from "./components/companylist/companylist.component";
import { Searchfield } from "./components/searchfield/searchfield.component";
class App extends Component {
constructor(props) {
super(props);
this.state = {
companies: [],
searchfield: "",
};
}
componentDidMount = () => {
const URL = "https://xxxxx/companies";
fetch(URL)
.then((response) => response.json())
.then((data) => this.setState({ companies: data }))
.catch((error) => {
console.error("Error", error);
});
};
render() {
const filteredCompanies = this.state.companies.filter((item) =>
item.name.toLowerCase().includes(this.state.searchfield.toLowerCase())
);
return (
<div>
<Searchfield
handleChange={(e) => this.setState({ searchfield: e.target.value })}
/>
<CompanyList companies={filteredCompanies} />
</div>
);
}
}
export default App;
Children component:
import React, { Component } from "react";
import { Company } from "../company/company.component";
export class CompanyList extends Component {
constructor(props) {
super(props);
this.state = {
newArray: [],
};
}
componentDidMount = () => {
const filledArray = this.props.companies.map((item) => {
let result;
fetch(`https://xxxxx/incomes/${item.id}`)
.then((response) => response.json())
.then((data) => {
let transactionsToFloat = data.incomes.map((item) =>
parseFloat(item.value)
);
result = transactionsToFloat.reduce((acc, num) => {
return acc + num;
}, 0);
result = Math.round(result * 100) / 100;
});
return {
id: item.id,
name: item.name,
city: item.city,
totalIncome: result,
};
});
this.setState({ newArray: filledArray });
console.log(this.props);
};
render() {
console.log(this.props);
return (
<div>
<table>
<thead>
<tr>
<th> Id </th>
<th> Name </th>
<th> City </th>
<th> Total income </th>
</tr>
</thead>
{this.props.companies.map((item) => (
<Company key={item.id} company={item} />
))}
</table>
</div>
);
}
}
componentWillMount() happens before render(). componentDidMount() happens after.
This is happening because of how React works fundamentally. React is supposed to feel fast, fluent and snappy. the application should never get logged up with http requests or asynchronous code. The answer is to use the lifecycle methods to control the DOM.
What does it mean when a component mounts?
It might be helpful to understand some of the React vocabularies a little better. When a component is mounted it is being inserted into the DOM. This is when a constructor is called. componentWillMount is pretty much synonymous with a constructor and is invoked around the same time. componentDidMount will only be called once after the first render.
componentWillMount --> render --> componentDidMount

Using ReactJs to fetch data from an api but getting a blank page with no error

Hello Guys kindly someone assist me with the issue i am having with my code. I am a newbie trying to learn react. i am trying to fetch data from an api. From the browser console i can see the data but when i try to return the data in the Dom i get a blank page. see my code below.
import React, { Component } from "react";
class FetchApi extends Component {
constructor(props) {
super(props);
this.state = {
person: []
};
}
componentDidMount() {
fetch("https://api.randomuser.me/")
.then(res => res.json())
.then(data => console.log(data))
.then(data => {
this.setState({
person: data
});
});
}
render() {
return (
<div>
{this.state.person &&
this.state.person.map(item => (
<li key={item.results.id}>
{item.results.gender} {item.results.location}
</li>
))}
</div>
);
}
}
export default FetchApi;
I have modified your code to the following. In some cases the way you are referencing the properties was wrong. Have made some changes in your componentDidMount and in the render method.
Sandbox for your reference: https://codesandbox.io/s/react-basic-example-nubu7
Hope this resolves
import React, { Component } from "react";
class App extends Component {
constructor(props) {
super(props);
this.state = {
person: []
};
}
componentDidMount() {
try {
fetch("https://api.randomuser.me/")
.then(res => res.json())
.then(data => {
this.setState({
person: data.results
});
});
} catch (e) {
console.log(e);
}
}
render() {
return (
<div>
{this.state.person &&
this.state.person.map(item => (
<li key={item.id.value}>
{item.gender} {item.location.city}
</li>
))}
</div>
);
}
}
export default App;
Check this codesandbox, the response from the fetch API is in this format
{
"results":[
{
"gender":"male",
"name":{
"title":"Monsieur",
"first":"Alois",
"last":"Fernandez"
},
"location":{
"street":{
"number":1856,
"name":"Rue Duquesne"
},
"city":"Untereggen",
"state":"Genève",
"country":"Switzerland",
"postcode":9650,
"coordinates":{
"latitude":"-50.1413",
"longitude":"-23.6337"
},
"timezone":{
"offset":"+5:30",
"description":"Bombay, Calcutta, Madras, New Delhi"
}
},
"email":"alois.fernandez#example.com",
"login":{
"uuid":"04b2ee45-cf07-4015-a5d8-2311f6dc28a1",
"username":"ticklishleopard228",
"password":"forward",
"salt":"V8bDcgux",
"md5":"d521c6488fc4644ccb7e670e9e67bc20",
"sha1":"9673afe219f27817c573a9cb727c209357d386ef",
"sha256":"c13a5bc77dc720a6cc46bc640680e9501225fc94c9bc6ba7fe203fe989a992a0"
},
"dob":{
"date":"1957-11-24T13:46:29.422Z",
"age":63
},
"registered":{
"date":"2003-05-18T19:56:11.397Z",
"age":17
},
"phone":"077 871 56 07",
"cell":"077 461 83 98",
"id":{
"name":"AVS",
"value":"756.1050.9271.56"
},
"picture":{
"large":"https://randomuser.me/api/portraits/men/8.jpg",
"medium":"https://randomuser.me/api/portraits/med/men/8.jpg",
"thumbnail":"https://randomuser.me/api/portraits/thumb/men/8.jpg"
},
"nat":"CH"
}
],
"info":{
"seed":"76a6b875b2ba81dd",
"results":1,
"page":1,
"version":"1.3"
}
}
So you have to set the person in the state to data.results instead of data and access the item in the results accordingly.

how to iterate through json data from api in react.js

I just started learning React and am trying to loop through an array of JSON data. However, I am getting some syntax errors. I'm trying to use the array.map function, but it's not working properly, and I'm not exactly sure how to implement it to make it display each element in the JSON array instead of just one. Any help is greatly appreciated - thanks!
import React, { Component } from 'react';
import axios from "axios";
import './App.css';
import UserForm from "./components/UserForm.js";
class App extends Component {
state = {
name: "",
stars: "",
icon: "",
trails: [], isLoaded: false
}
getUser = (e) => {
e.preventDefault();
const address = e.target.elements.address.value;
if (address) {
axios.get(`https://www.hikingproject.com/data/get-trails?lat=40.0274&lon=-105.2519&maxDistance=10&key=200279581-dd891420fa2c470dbb683b34e017062a`)
.then((res) => {
console.log(res);
const trailList = res.data.trails.map((trail) => {
console.log(trail.name)
console.log(trail.stars)
return <div> <p>{trail.name}</p> </div>
})
this.setState({ trails: trailList, isLoaded: true });
const name = res.data.trails.name;
const stars = res.data.trails.stars;
const icon = res.data.trails.imgMedium;
this.setState({ name });
this.setState({ stars });
this.setState({ icon });
})
}
else return;
}
render() {
return (
<div>
<div className="App">
<header className="App-header">
<h1 className="App-title">HTTP Calls in React</h1>
</header>
<UserForm getUser={this.getUser} />
<div className="newmessage">
{this.state.trails.map((obj) => {
return(
<div>
<p>{obj.name}</p> >
<p> {obj.stars}</p>
</div>
);
}}
</div>
</div>
</div>
</div>
);
}
};
export default App;
A good start would be to fetch your data in the componentDidMount either with fetch or axios. Never used axios, so I am going to answer the question with fetch
Leave the constructor as it is. Then write a componentDidMount like so:
componentDidMount() {
fetch('https://www.hikingproject.com/data/get-trails?lat=40.0274&lon=-105.2519&maxDistance=10&key=200279581-dd891420fa2c470dbb683b34e017062a')
.then(res => res.json())
.then(data => this.setState({ trails: data.trails }))
.catch(e => console.log(e))
}
then in a sub-render method, such as renderData, write the following code:
renderData() {
if (!this.state.trails) {
return null;
}
return this.state.trails.map(trail => <p>{trail.name}</p>);
}
Then call {this.renderData()} in your render
render() {
return (
<div>{this.renderData()}</div>
)
}
This code has been tested on my local environment and it was working as it should.

Resources