I am new to React. I am just trying to build a table of data coming from an API which I got it fetching. But now I want to render the data into a table.
My issue is that one of the columns is a logo that comes from a URL index.php?Action=GetLogo&id=51
. The number of results is more than 300. Even though I am already using pagination the table rendering is slow, particularly the logo
column. All the data gets loaded, but I can see each logo being rendered row by row, giving the user a slow experience.
Is there any way how React can approach to solve this issue? Someone please point me in the right direction how to tackle this.
Thanks everyone.
Update
Someone has advised me to use a sync/await function to load the images. Then I am updating the code. However my main issue is still with me: loading the data particularly the logo column. All the data gets rendered, but not the logo column, then each logo starts to render one by one looking very slow. I thought the async/await
function would alleviate that.
import React from 'react';
import ReactDOM from 'react-dom';
import FormData from 'form-data';
class FilterableSupplierTable extends React.Component {
constructor(props) {
super(props);
this.state = {
suppliers: []
}
}
componentDidMount() {
let formData = new FormData();
formData.append('AjaxMethodName', 'GetSuppliers');
const options = {
method: 'POST',
headers: {
'Accept': '*/*'
},
body: formData
};
fetch(`?Model=route_to_controller`, options)
.then(response => response.json())
.then(data => {
this.setState({ suppliers: JSON.parse(data.response_data) })
});
}
async getLogos(suppliers) {
return await Promise.all(
suppliers.map(async supplier => {
supplier.new_logo = !!supplier.has_logo ?
<img style={{maxWidth: "100px"}} src={supplier.logo} alt={supplier.supplier_id} /> :
<i className="icon icon-warning no_logo"> No Logo</i>;
return supplier;
});
);
}
render() {
const rows = [];
const suppliers = this.state.suppliers;
this.getLogos(suppliers)
.then(results => {
results.map(supplier => {
rows.push(
<tr>
{/* supplier.logo is index.php?Action=GetLogo&id=51, etc */}
<td><img src={supplier.new_logo} /></td>
<td>{supplier.name}</td>
</tr>
);
});
});
return (
<table>
<thead>
<tr>
<th colSpan="4">Suppliers</th>
</tr>
<tr>
<th>Logo</th>
<th>Name</th>
</tr>
</thead>
<tbody>{rows}</tbody>
</table>
);
}
}
ReactDOM.render(
<FilterableSupplierTable />,
document.getElementById('suppliers_list')
);
Your issue can be solved with a component that updates a "global loading state".
Only after all images have updated that they finished loading, they become visible together:
function MyImage(props) {
const onLoad = () => {
props.onLoad();
};
return (
<div>
{props.isCurrentlyLoading && <div>Loading</div>}
<img
src={props.src}
onLoad={onLoad}
style={
props.isCurrentlyLoading
? { width: "0", height: "0" } // You can also use opacity, etc.
: { width: 100, height: 100 }
}
/>
</div>
);
}
function ImagesBatch() {
const [loadedImagesCounter, setLoadedImagesCounter] = useState(0);
const [isAllLoaded, setIsAllLoaded] = useState(false);
const updateLoading = () => {
if (loadedImagesCounter + 1 === imagesUrls.length) {
setIsAllLoaded(true);
}
setLoadedImagesCounter(prev => prev + 1);
};
return (
<div>
{imagesUrls.map((url, index) => {
return (
<MyImage
key={url}
src={url}
index={index + 1}
isCurrentlyLoading={!isAllLoaded}
onLoad={updateLoading}
/>
);
})}
</div>
);
}
You can check the full code here (preferably with an open console), where I used an arbitrary ~6MB image, as an example.
I think you got your issues resolved. But, apart from that I suggest you checkout React Virtualized, https://github.com/bvaughn/react-virtualized
Hope this helps some day.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With