Create a JSON editor
In this tutorial, we will create a JSON editor Extension with React and the react-json-view package. Our application will display a JSON file's content and allow users to edit, add or delete properties directly from the editor. When a user finishes editing the JSON, the file will automatically update with the changes.
Prequisites
This tutorial assumes that you have a basic knowledge and understanding of web development and React.
Set up your Repl
- Fork the Replit React Extension Template.
- Install the
react-json-view
package withnpm install --force react-json-view
. The package uses React 17 as a peer dependency but works fine with React 18 as well.
Configure the manifest file
Configure the title and description in public/extension.json
(the Extension manifest file).
{
"name": "JSON Editor",
"description": "A viewer/editor for JSON files, providing code folding and structured editing",
"tags": ["editor"]
}
Add the fileHandlers
property to extension.json
and provide a handler for JSON files. This tells Replit that your extension handles a particular file pattern using a page provided by your extension at the handler
path. In this case, the handler is /
, meaning that Replit shows the page at the root as the handler for all .json
files
"fileHandlers": [
{
"glob": "*.json",
"handler": "/"
}
],
Build the JSON editor
Import the following dependencies in src/App.jsx
.
import * as React from "react";
import ReactJson from "react-json-view";
import "./App.css";
import {
useReplit,
useReplitEffect,
useWatchTextFile,
} from "@replit/extensions-react";
Remove all the existing code from the App
function and a state variable path
, which will point to the JSON file your Extension will render.
function App() {
const [path, setPath] = React.useState(null);
return <div>My app</div>;
}
Initialize the Handshake
Initialize the handshake and derive the status
and error
properties from the useReplit hook
within the App
function.
The status
property is an enumerated value indicating whether the handshake connection with Replit is loading
, ready
, or has resulted in an error
.
function App() {
...
const { status, error } = useReplit();
...
}
Get the File Path
Use the useReplitEffect
hook and set the path
state to the extensionPort
's file path. This will set the path
state once the handshake between Replit and your Extension has been established.
useReplitEffect(async ({ extensionPort }) => {
const filePath = await extensionPort.filePath;
setPath(filePath);
}, []);
Create the File Watcher
You can easily create a file watcher with the useWatchTextFile
hook. Call the hook, pass in the file path, and derive content
and writeChange
from it.
const { content, writeChange } = useWatchTextFile({
filePath: path,
});
Reflecting file contents
Create a parsedContent
React Memo that returns the content
file value as parsed JSON. If there is an error parsing it, return null
instead.
The React useMemo hook caches a result based on an array of dependencies between re-renders to improve performance. Caching reduces the amount of computing required in a process, ultimately improving performance.
const parsedContent = React.useMemo(() => {
try {
return JSON.parse(content);
} catch (e) {
return null;
}
}, [content]);
Handle file changes
Create a function which handles changes from the react-json-view editor component. The updated_src
property passed into this function is a JSON object.
Stringify the JSON object and then write it to the JSON file using the writeChange
function.
Finally, update the setContent
state to reflect the contents of the file.
const handleChange = async ({ updated_src: newContent }) => {
const stringified = JSON.stringify(newContent, null, 2);
writeChange({
from: 0,
to: content.length,
insert: stringified,
});
};
Build the UI
It's time to start building the UI.
First, handle loading and error states.
function App() {
...
if (status === "error") {
return <main>
<div className="notice error">{error.toString()}</div>
</main>
}
else if (status === "loading") {
return <main>
<div className="notice">Loading...</div>
</main>
}
else if (status === "ready") {
return <main>Ready</main>
}
}
Install the Extension by opening up the Command Bar (cmd/ctrl + k), navigating to Extensions, and selecting From this Repl. The extension should load and display "Ready" almost instantly.
If you open the webview, your extension should load for a few seconds and fail. Extensions should be developed and used within the correct pane rather than the webview.
If both path
and content
are valid strings, render the editor. If not, tell the user to select a file.
else if (status === "ready") {
return <main>
{path && content ?
<ReactJson
style={{ width: '100vw', height: "100vh", padding: "1em" }}
theme="ocean"
displayDataTypes={false}
src={parsedContent}
onEdit={handleChange}
onAdd={handleChange}
onDelete={handleChange}
/> :
<div className="notice">
Please select a file
</div>
}
</main>
}
That's it. Now install and load your Extension, and it should work.
Style your Extension
Right now, the Extension has barely any styles applied to it. To make it look more polished, paste the following into App.css
:
html,
body {
margin: 0;
padding: 0;
height: 100%;
width: 100%;
display: flex;
font-family: sans-serif;
background: black;
color: white;
}
#root,
main {
flex-grow: 1;
display: flex;
width: 100%;
}
.notice {
flex-grow: 1;
align-self: center;
justify-self: center;
text-align: center;
color: white;
font-size: 24px;
}
Your Extension is now complete! Install it, press the kebab menu on a JSON file in the file tree and then select "Open with JSON Editor" to start editing your JSON files with ease.