Electron Adventures: Episode 22: File Manager in React
Tomasz Wegrzanowski
Posted on August 14, 2021
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
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",
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()
})
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 }
)
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>
)
))}
</>
)
}
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:
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.
Posted on August 14, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
June 9, 2020