DEV Community

Tomasz Wegrzanowski
Tomasz Wegrzanowski

Posted on • Updated on

Electron Adventures: Episode 22: File Manager in React

I plan to mostly use Svelte in this series, but if you want to use React instead, that's also great!

This episode is a React version of episode 21 - setting up some foundation for a small file manager project.

As I'll be adding new features to it in future episodes, you should have no trouble to code in React what I'll be doing in Svelte.

And really, if you want to follow along in Vue or any other framework, it should be pretty easy to do. Other than promises, I'm not using anything complicated on the frontend, and everything in backend and preload code will be identical.

Getting started

I'll follow steps from episode 14, and create a new React electron app. We'll need one extra library react-use-promise as we'll be making extensive use of promises in this project, and using bare useEffect for them gets rather awkward.

$ npx create-react-app episode-22-file-manager-in-react --use-npm --template ready
$ cd episode-22-file-manager-in-react
$ npm i --save-dev electron
$ npm i --save react-use-promise
Enter fullscreen mode Exit fullscreen mode

And just as before we need to modify package.json so it doesn't try to open web browser:

"start": "BROWSER=none react-scripts start",
Enter fullscreen mode Exit fullscreen mode

And delete all unneeded files.

Backend

It's identical to what we had before:

let { app, BrowserWindow } = require("electron")

function createWindow() {
  let win = new BrowserWindow({
    webPreferences: {
      preload: `${__dirname}/preload.js`,
    },
  })
  win.maximize()
  win.loadURL("http://localhost:3000/")
}

app.on("ready", createWindow)

app.on("window-all-closed", () => {
  app.quit()
})
Enter fullscreen mode Exit fullscreen mode

Preload code

Preload code is identical to what we had in Svelte version - and it will stay the same regardless of framework.

let { readdir } = require("fs/promises")
let { contextBridge } = require("electron")

let directoryContents = async (path) => {
  let results = await readdir(path, {withFileTypes: true})
  return results.map(entry => ({
    name: entry.name,
    type: entry.isDirectory() ? "directory" : "file",
  }))
}

let currentDirectory = () => {
  return process.cwd()
}

contextBridge.exposeInMainWorld(
  "api", { directoryContents, currentDirectory }
)
Enter fullscreen mode Exit fullscreen mode

src/App.js

And here's the app:

import React, { useState } from "react"
import usePromise from "react-use-promise";

export default (props) => {
  let [directory, setDirectory] = useState(window.api.currentDirectory())
  let isRoot = (directory === "/")

  let [files, filesError, filesState] = usePromise(() => (
    window.api.directoryContents(directory)
  ), [directory])

  let navigate = (path) => {
    if (directory === "/") {
      setDirectory("/" + path)
    } else {
      setDirectory(directory + "/" + path)
    }
  }
  let navigateUp = () => {
    setDirectory(directory.split("/").slice(0, -1).join("/") || "/")
  }

  return (
    <>
      <h1>{directory}</h1>
      {!isRoot && <div><button onClick={() => navigateUp()}>..</button></div> }
      {files && files.map((entry, i) => (
        (entry.type === "directory") ? (
          <div key={i}>
            <button onClick={() => navigate(entry.name)}>{entry.name}</button>
          </div>
        ) : (
            <div key={i}>{entry.name}</div>
        )
      ))}
    </>
  )
}
Enter fullscreen mode Exit fullscreen mode

Other than usePromise, it doesn't do anything too unusual. I exposed filesError and filesState here so you can display a nice message while waiting or if something went wrong, even though we won't be doing this to keep the code concise.

You might consider using some filepath manipulation library instead of chopping and appending / - also to support Windows properly.

Results

Here's the result:

Episode 22 Screenshot

In the next few episodes we'll be adding a lot of new features to the app. I'll be showing the Svelte version, but nothing will be too Svelte specific, so you should definitely follow along in React if you prefer.

As usual, all the code for the episode is here.

Discussion (0)