Search code examples
node.jsreactjscreate-react-app

dynamic File inside folders not reading with React and preval.macro


I am trying to build an app which will show list of items read from a folder inside my create-react-app and it will read and show the file inside the folder.

import preval from 'preval.macro';
import '../index.css'

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

const AtomList = () => {

    const [newFileName, setNewFileName] = useState('avatar');


    const fileUnderComponent = preval
        `   const fs = require('fs')
            const path = require('path');
            const file = fs.readFileSync('src/atoms/${newFileName}/${newFileName}.js');
            module.exports = file.toString();
        `;

    const setNewComponent = (componentName) => {
        setNewFileName(componentName)
    }

    const eachAtom = (component) => {
        return (
            <ul><li className="list-item-group" onClick={() => setNewComponent(component)}>{component}</li></ul>
        )
    }

    return (
        <div>
            <div className="list-group inlineBlock atomList width20">
                <div>List of Reusable Components</div>
                {components.map((component, i) => {
                    return eachAtom(component)
                })}
            </div>
            <div className="list-group inlineBlock atomDescription width80">
                {typeof fileUnderComponent === 'string' ? fileUnderComponent : null}
            </div>

        </div>
    )
}

export default AtomList

In the above component, if i hardcode newFileName as 'avatar' the contents of the file renders but when i switch the components on clicking them the newFileName changes but fileUnderComponent doesn't render anything.

I think probably due to readFileSync operation which takes some time. I have tried putting it in setTimeout but no luck.

Where could i go wrong?


Solution

  • The preval evaluates the code at build time therefore it won't be possible to run it on updating your state. The following code to extract content of files for preval works fine.

    const contentOfComponents = preval`
    const fs = require("fs");
    const files = fs.readdirSync("src/atoms");
    const content = files.map(filename => {
      const fs = require("fs");
      const url = 'src/atoms/'+filename+'/'+filename+'.js';
      return fs.readFileSync(url).toString();
    })
    module.exports = content`;
    

    You can use this contentOfComponents which is an array of the contents of the files by mapping them against your components array and then displaying the appropriate content when the filename is clicked.

    Code From the Question after incorporating the changes as mentioned above

    import React, { useState } from "react";
    import preval from "preval.macro";
    import shortid from "shortid";
    
    const components = preval`
    const fs = require("fs");
    const files = fs.readdirSync("src/atoms");
    module.exports = files`;
    
    const contentOfComponents = preval`
    const fs = require("fs");
    const files = fs.readdirSync("src/atoms");
    const content = files.map(filename => {
      const fs = require("fs");
      const url = 'src/atoms/'+filename+'/'+filename+'.js';
      return fs.readFileSync(url).toString();
    })
    module.exports = content`;
    
    const AtomList = () => {
      const [fileUnderComponent, setFileUnderComponent] = useState(null);
    
      const setNewComponent = componentName => {
        const _index = components.indexOf(componentName);
        setFileUnderComponent(contentOfComponents[_index]);
      };
    
      const eachAtom = component => {
        return (
          <li
            key={shortid.generate()}
            className="list-item-group"
            onClick={() => setNewComponent(component)}
          >
            {component}
          </li>
        );
      };
    
      return (
        <div>
          <div className="list-group inlineBlock atomList width20">
            <div>List of Reusable Components</div>
            <ul>
              {components.map(component => {
                return eachAtom(component);
              })}
            </ul>
          </div>
          <div className="list-group inlineBlock atomDescription width80">
            {typeof fileUnderComponent === "string" ? fileUnderComponent : null}
          </div>
        </div>
      );
    };
    
    export default AtomList;
    
    

    Also your eachAtom function doesn't need to return a <ul>.

    Discarded! This was the first impression

    This could be a syntactical error. In your line const file = fs.readFileSync('src/atoms/${newFileName}/${newFileName}.js'); are you sure you are using back-ticks and not single quotes?

    This might solve it:

    const file = fs.readFileSync(`src/atoms/${newFileName}/${newFileName}.js`);

    back-tick key is usually on the top left corner just below the Esc key and to the left of number 1 key.