Yilmaz
Yilmaz

Reputation: 49351

first download image button click is downloading .html file in react.js - next.js

This is the component I reproduced:

import Image from "next/image";
import { useState } from "react";
export default function Home() {
  const [downloadURL, setDownloadURL] = useState("");

  const download = async () => {
    const result = await fetch("http://localhost:3000/test.jpg", {
      method: "GET",
      headers: {},
    });
    const blob = await result.blob();
    const url = URL.createObjectURL(blob);
    setDownloadURL(url);
  };

  const handleDownload = async (e) => {
    try {
      await download();
      URL.revokeObjectURL(downloadURL);
    } catch (error) {
      console.error(error);
    }
  };
  return (
    <div className=" bg-gray-500 bg-opacity-75 transition-opacity flex flex-col justify-center  items-center">
      <Image src="/test.jpg" width={500} height={600} className="mb-2 " />
      <button
        onClick={handleDownload}
        type="button"
        className="flex-1 content-center text-center bg-indigo-600 py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
      >
        <a href={downloadURL} download={"test"}>
          Download Image
        </a>
      </button>
    </div>
  );
}

in the first click, I download .html file and in next clicks I get the image downloaded. but I could not figure out what is causing the first click downloading the html file.

reproducible github repo

enter image description here

Upvotes: 0

Views: 3880

Answers (1)

Sergey Sosunov
Sergey Sosunov

Reputation: 4600

Few mistakes with that approach:

  1. Event bubbling, the click event is handled by <a> before the <button>'s onClick.
  2. Dependency on a state variable changed during 1 click event, you are trying to change <a> href which is bind to state, but the state is not updated immediatelly, it will be updated only on next render, but the click event will not wait for that (skipping the fact it is done with async operation).

What I recommend: remove <a> completely and use old classic download function which creates <a> on the fly and executes "click" on it:

function Home() {
  const download = (filename, content) => {
    var element = document.createElement("a");
    element.setAttribute("href", content);
    element.setAttribute("download", filename);
    element.style.display = "none";
    document.body.appendChild(element);

    element.click();

    document.body.removeChild(element);
  };

  const handleDownload = async (e) => {
    try {
      const result = await fetch("assets/test.png", {
        method: "GET",
        headers: {}
      });
      const blob = await result.blob();
      const url = URL.createObjectURL(blob);
      download("test", url);
      URL.revokeObjectURL(url);
    } catch (error) {
      console.error(error);
    }
  };
  return (
    <div>
      <img src="/assets/test.png" width={100} height={100} />
      <button onClick={handleDownload} type="button">
        Download Image
      </button>
    </div>
  );
}

Edit React dynamic download

Sorry for simplifying your code a bit.

Upvotes: 3

Related Questions