Search code examples
javascriptreactjsfile-ioblob

React:write to json file or export/download [no server]


I got really confused with file I/O in JS/TS. most examples I see works with DOM and has browser-based solutions.

Also, I did not understand how to make fs work, it seems to need a webpack config, where I use CRA and do not want to eject.

in a React component I want to fetch some data from a server then save them as a JSON file in the project folder (the same path, root, public folder, no matter) or directly download (no button needed).

//data type just in case
inteface IAllData{ name:string; allData:IData[];}

so after fetching some data want to save them to name.json

public componentDidMount(){
   this.fetchData().then(()=>this.saveData())
}

public async fetchData(){/* sets data in state*/}

public saveData(){
    const {myData}=this.state;
    const fileName=myData.name;
    const json=JSON.stringify(myData);
    const blob=new Blob([json],{type:'application/json'})
    /* How to write/download this blob as a file? */
}

here trying window.navigator.msSaveOrOpenBlob(blob, 'export.json'); did not work

note: I know it has security risks, it is not for production. save the file in the project folder is preferred but a download is totally ok.


Solution

  • I had a blob containing data and I had found a solution on stackoverflow and manipulated a bit, and succeded to download as a xlsx file. I am adding my code below, it might help you, too.

    const blob =  await res.blob(); // blob just as yours
    const href = await URL.createObjectURL(blob);
    const link = document.createElement('a');
    link.href = href;
    link.download = "file.xlsx";
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
    

    EDIT: You can use the function below, but be sure to switch out fileName and myData from this.state to something that will work in your application.

    const downloadFile = () => {
      const { myData } = this.state; // I am assuming that "this.state.myData"
                                     // is an object and I wrote it to file as
                                     // json
    
      // create file in browser
      const fileName = "my-file";
      const json = JSON.stringify(myData, null, 2);
      const blob = new Blob([json], { type: "application/json" });
      const href = URL.createObjectURL(blob);
    
      // create "a" HTLM element with href to file
      const link = document.createElement("a");
      link.href = href;
      link.download = fileName + ".json";
      document.body.appendChild(link);
      link.click();
    
      // clean up "a" element & remove ObjectURL
      document.body.removeChild(link);
      URL.revokeObjectURL(href);
    }
    

    More documentation for URL.createObjectURL is available on MDN. It's critical to release the object with URL.revokeObjectURL to prevent a memory leak. In the function above, since we've already downloaded the file, we can immediately revoke the object.

    Each time you call createObjectURL(), a new object URL is created, even if you've already created one for the same object. Each of these must be released by calling URL.revokeObjectURL() when you no longer need them.

    Browsers will release object URLs automatically when the document is unloaded; however, for optimal performance and memory usage, if there are safe times when you can explicitly unload them, you should do so.