React table slow loading when displaying img rows
Asked Answered
S

2

5

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">&nbsp;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')
    );
Smilax answered 12/4, 2020 at 10:5 Comment(11)
sometimes the size of the images can increase load time in dev enviroment are you certain the images are web optimized?Geld
'Lagging' can mean more things. Try using await or a Suspense blockKanchenjunga
@AdrianPascu I will check that thanks I have also updated the title.Smilax
Could it be a case where these images are extremely 'heavy'? High resolution and also not optimized which can lead to large filesizes to render in the table component?Teatime
@AdrianPascu. Can you give me an example how can I use those blocks?Smilax
Async/Await is a JS feature to await the response o a promise. You should read on how to use it. The official MDN doc is pretty good. As for the Suspense block, here is a pretty good example blog.logrocket.com/react-suspense-for-data-fetching . It uses hooks instead of class based components, but hooks are really not hard to understand and are very well documented in the react docs. Everything can be mapped to class components if you don't want to switch to hooksKanchenjunga
Why do you use async inside getLogos? There's nothing asynchronous inside that map loopDisherison
@Disherison Thanks, yes I removed the async and now my array looks better (I'll update my question). 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.Smilax
@Smilax The alternative would be to wait until all logos are loaded, which will result in a blank column for a longer time period. Is that what you want?Disherison
@GalAbra. No, I would like to wait and load the logos, don't display anything, maybe just a spinning loading wheel. Then, when all the logos have loaded, render everything. Is this possible? Is the best thing to do in these cases?Smilax
@Disherison sorry man my array still looks empty. So, nothing gets rendered. I am printing this array into the console and when I expand it, it has all the rows, but when collapsed seems empty.Smilax
D
7

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.

Disherison answered 19/4, 2020 at 8:35 Comment(5)
I am checking thank you. I won't even need my getLogos (async/await) function if I use yours? My images are on the KBs :pSmilax
@Smilax That's right. You only need to filter your clients array, to make sure you only count those with a valid image urlDisherison
How can I have one single Loading message for all the elements instead of a Loading message for each image?Smilax
Yes, just remove it from MyImage and add in ImagesBatchDisherison
I have a long way to go into React... Thanks @DisherisonSmilax
S
1

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.

Symmetrize answered 25/4, 2020 at 20:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.