Electron Adventures: Episode 22: File Manager in React
Tags: electron, javascript, react
Published: 2021-08-14 23:03:00

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`](https://github.com/bsonntag/react-use-promise) as we'll be making extensive use of promises in this project, and using bare `useEffect` for them gets rather awkward.

```shell
$ 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
```

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

```javascript:
"start": "BROWSER=none react-scripts start",
```

And delete all unneeded files.

### Backend

It's identical to what we had before:

```javascript
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()
})
```

### Preload code

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

```javascript
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 }
)
```

### `src/App.js`

And here's the app:

```jsx
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>
        )
      ))}
    </>
  )
}
```

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ynch5tyv719rufoxak8m.png)

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](https://github.com/taw/electron-adventures/tree/master/episode-22-file-manager-in-react).