Export Excel(XLSX) file in react with images and custom styles in ReactJs

sumankalia

Suman Kumar

Posted on February 2, 2023

Export Excel(XLSX) file in react with images and custom styles in ReactJs

If you have ever worked with react and NodeJs. Then you have surely added the export excel file in any of your projects. Exporting an excel file might be easy but sometimes we have to export a more custom excel file with some custom styles as marking cells with different colors and sometimes we also need to add the images in the same file.

For this, you have different options to export the file whether in NodeJs or ReactJs. But, today we will discuss a way to export a feature-rich excel file in ReactJs. We are exporting it in ReactJs. So, it would be useful for both React and full-stack developers.


For this, we are going to use a third-party package - ExecelJs
Why ExcelJs- because it offers a lot of features and works with bulk data. I have tested it with a spreadsheet file with over 50000 records.
This package is used to read, manipulate and write spreadsheet data and styles to XLSX and JSON


We will understand the process by creating a react app by create-react-app.
We will add a free API to get data - https://dummyjson.com/products


In the app, we will first load the data from the API. We will get 30 product data with details of id, title, price, photo, etc. Then, we will create a table with product data id, title, price, thumbnail, etc.

Image description

Our primary focus is to export the same table's data in a spreadsheet(xlsx) file. The spreadsheet's data will look something like this

Image description

We have also added a conditional styling for the price if the price is between 50 and 1000 then the cell will be red colour.

Implementation

First, we will install the ExcelJs package in our project.

Then, we will call the API to get the data and set it in the state on the first-page load.



const [data, setData] = useState([]);
  useEffect(() => {
    fetch("https://dummyjson.com/products")
      .then((res) => res.json())
      .then(async (data) => {
        setData(data);
      })
      .then((json) => console.log(json));
  }, []);


Enter fullscreen mode Exit fullscreen mode

Now, we will add a button as "Export" on top of the table, when someone clicks on this button it will create a spreadsheet and download it in the front end side.

The export button above the table on extreme right

Now we will create a new workbook with Exceljs.



const workbook = new ExcelJS.Workbook();


Enter fullscreen mode Exit fullscreen mode

Then, we will add a sheet to this workbook



const sheet = workbook.addWorksheet("My Sheet");


Enter fullscreen mode Exit fullscreen mode

Now, we have to add an image to every row in the spreadsheet. So, we will add some default height to every row in the sheet.



sheet.properties.defaultRowHeight = 80;


Enter fullscreen mode Exit fullscreen mode

We have the first row of the sheet as the header of the sheet with different column titles. So we will add some styles to this as well ie. some borders, fonts, fill, etc.



sheet.getRow(1).border = {
      top: { style: "thick", color: { argb: "FFFF0000" } },
      left: { style: "thick", color: { argb: "000000FF" } },
      bottom: { style: "thick", color: { argb: "F08080" } },
      right: { style: "thick", color: { argb: "FF00FF00" } },
    };

    sheet.getRow(1).fill = {
      type: "pattern",
      pattern: "darkVertical",
      fgColor: { argb: "FFFF00" },
    };

    sheet.getRow(1).font = {
      name: "Comic Sans MS",
      family: 4,
      size: 16,
      bold: true,
    };


Enter fullscreen mode Exit fullscreen mode

Now, we will define the columns, this will be an array of objects. And each object has three keys header, key, and width. The "header" is the title of the column, the "key" must be the same as the JSON key of data to map data in the column, and the "width" is the width of the column.



sheet.columns = [
      {
        header: "Id",
        key: "id",
        width: 10,
      },
      { header: "Title", key: "title", width: 32 },
      {
        header: "Brand",
        key: "brand",
        width: 20,
      },
      {
        header: "Category",
        key: "category",
        width: 20,
      },
      {
        header: "Price",
        key: "price",
        width: 15,
      },
      {
        header: "Rating",
        key: "rating",
        width: 10,
      },
      {
        header: "Photo",
        key: "thumbnail",
        width: 30,
      },
    ];


Enter fullscreen mode Exit fullscreen mode

Now, it's time to add the data in each column with the same key name we have in column objects.



data?.products?.map((product) => {
        sheet.addRow({
          id: product?.id,
          title: product?.title,
          brand: product?.brand,
          category: product?.category,
          price: product?.price,
          rating: product?.rating,
        });
      })


Enter fullscreen mode Exit fullscreen mode

The above code is to add simple data to the sheet. But for the images, we need to have a different approach.



const promise = Promise.all(
      data?.products?.map(async (product, index) => {
        const rowNumber = index + 1;
        sheet.addRow({
          id: product?.id,
          title: product?.title,
          brand: product?.brand,
          category: product?.category,
          price: product?.price,
          rating: product?.rating,
        });

        const result = await toDataURL(product?.thumbnail);
        const splitted = product?.thumbnail.split(".");
        const extName = splitted[splitted.length - 1];

        const imageId2 = workbook.addImage({
          base64: result.base64Url,
          extension: extName,
        });

        sheet.addImage(imageId2, {
          tl: { col: 6, row: rowNumber },
          ext: { width: 100, height: 100 },
        });
      })
    );


Enter fullscreen mode Exit fullscreen mode

Here, we are calling an async method "toDataURL", in which we are passing the image URL and getting the base64 encoded image.

Then, we are getting the image extension in the next step ie. png, jpeg, etc.

Then, we will create the imageId2 object, by base64 image string and the image extension. And, in the next step, we pass it to the sheet.addImage() method with the "tl" key(column and row) and "ext" key(to define the height and width).



const toDataURL = (url) => {
  const promise = new Promise((resolve, reject) => {
    var xhr = new XMLHttpRequest();
    xhr.onload = function () {
      var reader = new FileReader();
      reader.readAsDataURL(xhr.response);
      reader.onloadend = function () {
        resolve({ base64Url: reader.result });
      };
    };
    xhr.open("GET", url);
    xhr.responseType = "blob";
    xhr.send();
  });

  return promise;
};


Enter fullscreen mode Exit fullscreen mode

The above method is to convert an image from URL to base64 encoded.
 
In this method, we will call the URL and get the image data, and convert it to a base64 encoded image.

After this step, we will have a promise. Now, in the "then" block of the promise we will add the price column styles to fill the cell red if the price is between 50 and 1000.



promise.then(() => {
      const priceCol = sheet.getColumn(5);

      // iterate over all current cells in this column
      priceCol.eachCell((cell) => {
        const cellValue = sheet.getCell(cell?.address).value;
        // add a condition to set styling
        if (cellValue > 50 && cellValue < 1000) {
          sheet.getCell(cell?.address).fill = {
            type: "pattern",
            pattern: "solid",
            fgColor: { argb: "FF0000" },
          };
        }
      });
    });


Enter fullscreen mode Exit fullscreen mode

Now, in the last step, we use the workbook writeBuffer method to create the xlsx file and download it as below.



workbook.xlsx.writeBuffer().then(function (data) {
        const blob = new Blob([data], {
          type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
        });
        const url = window.URL.createObjectURL(blob);
        const anchor = document.createElement("a");
        anchor.href = url;
        anchor.download = "download.xlsx";
        anchor.click();
        window.URL.revokeObjectURL(url);
      });


Enter fullscreen mode Exit fullscreen mode

In the above code snippet we are getting data after the promise is resolved, then we will create it to buffer and define the type of the file ie. spreadsheet.

 Then we are creating a temporary anchor to download the file by giving the file name "download.xlsx". And we will click this anchor programmatically and download the file and revoke the anchor in the next step as well.

Now finally the download function and price styling step will look something like this



promise.then(() => {
      const priceCol = sheet.getColumn(5);

      // iterate over all current cells in this column
      priceCol.eachCell((cell) => {
        const cellValue = sheet.getCell(cell?.address).value;
        // add a condition to set styling
        if (cellValue > 50 && cellValue < 1000) {
          sheet.getCell(cell?.address).fill = {
            type: "pattern",
            pattern: "solid",
            fgColor: { argb: "FF0000" },
          };
        }
      });

      workbook.xlsx.writeBuffer().then(function (data) {
        const blob = new Blob([data], {
          type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
        });
        const url = window.URL.createObjectURL(blob);
        const anchor = document.createElement("a");
        anchor.href = url;
        anchor.download = "download.xlsx";
        anchor.click();
        window.URL.revokeObjectURL(url);
      });
    });


Enter fullscreen mode Exit fullscreen mode

GitHub Link- https://github.com/sumankalia/export-xlsx-react

Live demo- https://63da86a84679e413b00fd738--dreamy-pithivier-43351e.netlify.app/

Youtube video link in Hindi -

If you find this article helpful, please do like this and follow me.

Thanks for reading

💖 💪 🙅 🚩
sumankalia
Suman Kumar

Posted on February 2, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related