8

I am currently using react with node js in my application.

I am trying to read from a text file, get different errors when I try different things. With research, the easiest path to read from a file is using 'fs', so I'll post the two ways I tried to read from a local file (client side).

I would appreciate it if anyone could help solve my errors, or even show me a different way of reading from a local file!

import React ...
import { readFile, readFileSync } from 'fs';
// other imports

class Background extends Component {
     // my constructor

     componentDidMount() {
         this.loadMe();
     }

     loadMe() {
          var file = './read_file';

          /* WAY 1: Asynchronously using readFile */
          readFile(file, function(err, data) {
               if (err) { console.log(err) }
               console.log(data);
          }

          /* WAY 2: Synchronously using readFileSync */
          var data = readFileSync(file);
          console.log(data);
     }
}

Obviously, I am not running both ways at the same time. I also essentially copied the code from other stack overflow answers and some tutorials online. I looked into FileReader but couldn't make it work.

The error I am currently receiving: TypeError: Object(...) is not a function pointing at var data = readFileSync(file);

Thank you!

lve
  • 307
  • 2
  • 5
  • 15
  • 1
    I think you are mixing the knowledge you read from node-js and React. React can't use 'fs' becuase fs if a node library. React runs on the browser, not in node so you need to use a FileReader and an input field. If every website could use fs to directly read from the client's disk, that would be a TERRIBLE security flaw! The solution is to create an field of type "file" (the typical "Select File" button) and then read the file with a FileReader. – Luis Paulo Jul 12 '19 at 13:29
  • 2
    Yeah, I'm relatively new to both so I'm not surprised that it's being mixed up. Thanks for that clarification, would you suggest using FileReader, as you mentioned, in that case? – lve Jul 12 '19 at 13:50
  • Yep. I've written an example for you. See my answer and, if it solves your problem, accept it, please :) – Luis Paulo Jul 12 '19 at 16:57

4 Answers4

2

fs is a library reserved to server JS env like NodeJS.

Mosè Raguzzini
  • 15,399
  • 1
  • 31
  • 43
  • Damn. I really want this functionality on the client side, not server side of my application. Do you know any other way to read data from a file? – lve Jul 12 '19 at 13:16
  • 1
    You can load files in public folder using library like fetch/axios or import it by relative url through webpack if they're static – Mosè Raguzzini Jul 12 '19 at 13:17
  • I could try that, my only issue is that the user needs to edit the file from time to time. For example, on the click of a button, it adds a new line to the file with certain information. Is that still possible? – lve Jul 12 '19 at 13:25
  • No, that's not possible by frontend-only. You need a backend to manage filesystem's operations. – Mosè Raguzzini Jul 12 '19 at 13:28
  • 3
    @MosèRaguzzini, you are misleading... it is indeed possible to read from a file on the client side. just not with 'fs'. A wrong answer sometimes is worse than no answer at all. – Luis Paulo Jul 12 '19 at 13:30
  • @LuisPaulo read carefully, I just suggested alternative ways to READ a file, but OP need to WRITE too. – Mosè Raguzzini Jul 12 '19 at 13:33
  • He can write too. See my answer. – Luis Paulo Jul 12 '19 at 16:55
1

It isn't possible to open and edit a file directly from the browser. It is, however, possible to open a file, edit and then download the edited file.

You need an input element with file type because this way the browsers can guarantee a page only accesses the files that the user explicitly selected. (Please tell me if this wasn't clear on the comments. I'll try to explain better if it wasn't.)

In the following example I'm going to use a textarea element to edit the file's contents but you can change it in code or however you like, once you have the contents in a string variable.

<!DOCTYPE html>
<html>
<head>
    <title>FileReader Example</title>
    <meta  charset="utf-8"/>
    <script>
        document.addEventListener('DOMContentLoaded',function() {
            var fileInput       = document.getElementById("fileInput");
            var textArea        = document.getElementById("fileEditor");
            var saveFileButton  = document.getElementById("saveFileButton");
            var downloadAnchor  = document.getElementById("downloadAnchor");

            function base64EncodeUnicode(str) {
                // First we escape the string using encodeURIComponent to get the UTF-8 encoding of the characters, 
                // then we convert the percent encodings into raw bytes, and finally feed it to btoa() function.
                utf8Bytes = encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function(match, p1) {
                        return String.fromCharCode('0x' + p1);
                });

                return btoa(utf8Bytes);
            }

            function handleInputFileChange(event) {
                //if we didnd't already have the "fileInput" var in scope, we could use "event.target" to get it
                if(fileInput.files.length>=1) {
                    //In this example, I'm putting the selected file's name in the title. You don't need to do this
                    document.title = fileInput.files[0].name;
                    downloadAnchor.setAttribute("download","edited_"+fileInput.files[0].name);
                }
                else {
                    document.title = "FileReader Example";
                    downloadAnchor.setAttribute("download","edited_file.txt");
                }
                var fr = new FileReader();
                fr.readAsText(fileInput.files[0]);
                fr.onload = function (event) {
                    //Both "event.target.result" and "fr.result" contain the file's contents (because "event.target" is === "fr")

                    textArea.value = event.target.result;
                    // OR
                    //textArea.value = fr.result;
                }
            }
            //The next is the fucntion returns a special kind of URL, a Data URL.
            //These kind of URLs don't point to a file, they ARE the data.
            //Read more here: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs
            function getDownloadableTextAreaContents() {
                return "data:text/plain,"+encodeURIComponent(textArea.value);
            }
            function onDownloadClick(event) {
                //https://stackoverflow.com/a/247261/6302540
                //var urlObject = "data:text/plain;base64,"+btoa(unescape(encodeURIComponent(textArea.value)));
                var urlObject = getDownloadableTextAreaContents();
                downloadAnchor.setAttribute("href",urlObject);
                downloadAnchor.click();
            }

            fileInput.addEventListener("change",handleInputFileChange);
            saveFileButton.addEventListener("click",onDownloadClick);
        },false);
    </script>
</head>
<body>
    <h1>File Reader Example:</h1>
    <input id="fileInput" type="file" accept=".txt"/>
    <textarea name="File Editor" id="fileEditor" placeholder="Your file's contents will show up here once you select the file!"></textarea>
    <button id="saveFileButton">Save File</button>
    <!--The next <a> tag is just a trick to make the browser download a file. It isn't displayed (style="display: none;")-->
    <a id="downloadAnchor" download="edited_file.txt" href="data:text/plain," style="display: none;"></a>
</body>
</html>

This is the example in plain JS but you can easily adapt this to react. A few of the adaptations are:

  • Delete the ids because you don't need them in React;
  • Instead of a simple textarea, you will have a controlled textarea component. Setting and getting the value of the text area is easy with controlled components. (If you don't understand React's state and props or how controlled components work, you could use references (which make the React code pretty much the same and plain JS) but I seriously do not recommend it because it is an anti-pattern and should only be used in rare situations like some very, very,,,, very fancy animations);
  • The change and click events are easily converted using onChange and onClick props;
  • To do the downloadAnchor.click(), you can follow this answer
  • The hidden anchor (<a>)'s href and download attributes can also be a normal prop which has a state value, for example:

In React:

<a download={this.state.downloadFilename} href={this.state.dataURL} style="display: none;"/>
Luis Paulo
  • 421
  • 5
  • 10
  • You can test this snippet of code. Just copy and paste it into a file on your Desktop like `fileReader.html` and then open it with your browser. – Luis Paulo Jul 12 '19 at 16:56
0

you can use something like https://github.com/kentcdodds/babel-plugin-preval to use fs modules in your front end react app.

install preval.macro with

npm i preval.macro --save

Then add this to your application

import preval from 'preval.macro';
const components = preval
    ` const fs = require('fs');
  const files = fs.readdirSync('src/atoms');
  module.exports = files;
`

The components will return the folders under src/atoms.

0

import file using import statement then pass it in fetch function to read it.

import file from '../documents/doc.pdf';

const bytes = await fetch(file).then(res => res.arrayBuffer())