JSONCrack Codebase Analysis — Part 4.2.1 — Editor — JsonEditor Component
Tags: javascript, webdev, opensource, react
Published: 2024-01-06 23:11:51

[jsoncrack](https://jsoncrack.com/editor) is a popular opensource tool used to visualise json into a mindmap. It is built using Next.js.

We, at [TThroo](https://tthroo.com/), love open source and perform codebase analysis on popular repositories, document, and provide a detailed explanation of the codebase. This enables OSS enthusiasts to learn and contribute to open source projects, and we also apply these learnings in our projects.

[Part 4.1](https://medium.com/@ram_89216/jsoncrack-codebase-analysis-part-4-1-editor-panes-component-5e2124ceb911) talks about `Panes` component that has this component `JsonEditor`

Do what does `JsonEditor` do? the below image shows what it is responsible for.

![JsonEditor](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ajr4b1r7pqaqgku9ty0p.png)

[JsonEditor.tsx](https://github.com/AykutSarac/jsoncrack.com/blob/main/src/containers/Editor/JsonEditor.tsx#L14) has about 23 lines at the time of writing this article.

```
import React from "react";
import styled from "styled-components";
import { MonacoEditor } from "src/components/MonacoEditor";

// import { PromptInput } from "src/components/PromptInput";

const StyledEditorWrapper = styled.div`
  display: flex;
  flex-direction: column;
  height: 100%;
  user-select: none;
`;

export const JsonEditor: React.FC = () => {
  return (
    <StyledEditorWrapper>
      {/* <PromptInput /> */}
      <MonacoEditor />
    </StyledEditorWrapper>
  );
};

export default JsonEditor;
```

Aye, commented code? anyways, You see this tool uses popular [MonacoEditor](https://github.com/microsoft/monaco-editor), a browser based open source code editor from Microsoft.

## MonacoEditor component

Now notice how we are now moving away from JsonEditor component to MonacoEditor. The author could have just put all the code related to `MonacoEditor` in this `JsonEditor` component, but no, it has been moved to `components` folder. One possible answer could be jsoncrack author must have decided to include PromtInput as part of JsonEditor, but it was commented. Purely, to add more features along side the monaco editor, I think `MonacoEditor` is made a separate new component. 

Let's now understand what [MonacoEditor](https://github.com/AykutSarac/jsoncrack.com/blob/main/src/components/MonacoEditor/index.tsx#L29) does.

Top down approach, this component returns the following:

```
return (
    <StyledWrapper>
      <Editor
        height="100%"
        language={fileType}
        theme={theme}
        value={contents}
        options={editorOptions}
        onValidate={errors => setError(errors[0]?.message)}
        onChange={contents => setContents({ contents, skipUpdate: true })}
        loading={<Loading message="Loading Monaco Editor..." loading />}
      />
    </StyledWrapper>
  );
```

Let's go through each prop in detail.

### `language={fileType}`

Where is `fileType` coming from? Checking the [MonacoEditor.tsx](https://github.com/AykutSarac/jsoncrack.com/blob/main/src/components/MonacoEditor/index.tsx) code, we see

`const fileType = useFile(state => state.format);`

[useFile](https://github.com/AykutSarac/jsoncrack.com/blob/main/src/store/useFile.ts#L89)? no worries, it is [zustand store](https://docs.pmnd.rs/zustand/getting-started/introduction).

This `useFile` has initial state as follows

```
const initialStates = {
  fileData: null as File | null,
  format: FileFormat.JSON,
  contents: defaultJson,
  error: null as any,
  hasChanges: false,
  jsonSchema: null as object | null,
};
```
Always look for what you are interested in, do not let the extra props, additions functions, fancy typescript type scare you away. It helps you stay focused. so what we are looking for is `FileFormat.JSON`

[FileFormat](https://github.com/AykutSarac/jsoncrack.com/blob/main/src/enums/file.enum.ts#L2)? it is simple enum file. This goes to say that jsoncrack author follows standard coding practices in not using magical strings or numbers instead relies on enums. 

Notice how we approached in understanding `fileType` prop, it was unknown but we carefully looked at imports and specifically looked for it without letting the code inside `useFile` scare us away :)

### theme={theme}

Let's take the same approach as we did for `fileType`. 

`const theme = useConfig(state => (state.darkmodeEnabled ? "vs-dark" : "light"));`

you will find the above snippet [here](https://github.com/AykutSarac/jsoncrack.com/blob/main/src/components/MonacoEditor/index.tsx#L36C1-L37C1)

What are the unknowns here? yes, `useConfig`

[useConfig](https://github.com/AykutSarac/jsoncrack.com/blob/main/src/store/useConfig.ts)

We are specifically looking for an initialState with the prop `darkModeEnabled`

```
const initialStates = {
  darkmodeEnabled: false,
  collapseButtonVisible: true,
  childrenCountVisible: true,
  imagePreviewEnabled: true,
  liveTransformEnabled: true,
  gesturesEnabled: false,
  rulersEnabled: true,
  viewMode: ViewMode.Graph,
};
```

You will find the above code snippet [here](https://github.com/AykutSarac/jsoncrack.com/blob/main/src/store/useConfig.ts#L6C1-L15C3)

So by default, `darkmodeEnabled` is set to false as part of initial state.

### value={contents}

Let's take the same approach as we did for `theme`. 

`const contents = useFile(state => state.contents);`

You will find the above code snippet [here](https://github.com/AykutSarac/jsoncrack.com/blob/main/src/components/MonacoEditor/index.tsx#L31C1-L32C1).

back to `useFile`, except this time we are interested in a different prop named `contents`. 

[useFile](https://github.com/AykutSarac/jsoncrack.com/blob/main/src/store/useFile.ts#L54) initialState 

```
const initialStates = {
  fileData: null as File | null,
  format: FileFormat.JSON,
  contents: defaultJson,
  error: null as any,
  hasChanges: false,
  jsonSchema: null as object | null,
};

```

Pick what you are interested in, yes, `contents`. what is a `defaulJson` ? the answer is in [import](https://github.com/AykutSarac/jsoncrack.com/blob/main/src/store/useFile.ts#L7) 

This is how you find answers, You have to be patient. There is a back and forth jumping between different files, that is because author has defined such a pattern. 

Meaning, all the zustand stores and inside a folder called store inside src. Large project complexity can be maintained well when you have well defined design pattern. You should keep it simple and know where to look for. Back to the code analysis now.

[defaultJson](https://github.com/AykutSarac/jsoncrack.com/blob/main/src/constants/data.ts) is used to render some placeholder json. Notice how this is inside `src/constants/data.ts`. Again, I am praising the way it is organised.

### options={editorOptions}

[editorOptions](https://github.com/AykutSarac/jsoncrack.com/blob/main/src/components/MonacoEditor/index.tsx#L14) are to provide your options for your monaco editor.

### onValidate={errors => setError(errors[0]?.message)}

[onValidate](https://github.com/AykutSarac/jsoncrack.com/blob/main/src/store/useFile.ts#L144) used to set errors in the `useFile` store

You can read more about it [here](https://www.npmjs.com/package/@monaco-editor/react#onvalidate)

### onChange={contents => setContents({ contents, skipUpdate: true })}

[setContents](https://github.com/AykutSarac/jsoncrack.com/blob/main/src/store/useFile.ts#L121) has few side effects.

```
setContents: async ({ contents, hasChanges = true, skipUpdate = false }) => {
    try {
      set({ ...(contents && { contents }), error: null, hasChanges });

      const isFetchURL = window.location.href.includes("?");
      const json = await contentToJson(get().contents, get().format);

      if (!useConfig.getState().liveTransformEnabled && skipUpdate) return;

      if (get().hasChanges && contents && contents.length < 80_000 && !isIframe() && !isFetchURL) {
        sessionStorage.setItem("content", contents);
        sessionStorage.setItem("format", get().format);
        set({ hasChanges: true });
      }

      debouncedUpdateJson(json);
    } catch (error: any) {
      if (error?.mark?.snippet) return set({ error: error.mark.snippet });
      if (error?.message) set({ error: error.message });
      useJson.setState({ loading: false });
      useGraph.setState({ loading: false });
    }
  },
```
The prop `setContents({ contents, skipUpdate: true })` only sets skipUpdate and contents, `setContents: async ({ contents, hasChanges = true, skipUpdate = false })` hasChanges is defaulted to true.

`set({ ...(contents && { contents }), error: null, hasChanges });` updates the contents in the `useFile` store.

`const isFetchURL = window.location.href.includes("?");` apparently, jsoncrack does not auto save your edits. You have to click on `save to cloud` in the `BottomBar` to save your json and then you will see your url changing to something like `https://jsoncrack.com/editor?json=0d15b1a928fdbd3c3283d1e6`

![Save to cloud](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2vlyoutlg7cpcy73um5n.png) I have done to understand what isFetchURL does. now we know why.

`const json = await contentToJson(get().contents, get().format);`

`contentToJson` function name indicates that it takes monacoeditor content and converts it to json. Let's see what code is. 

This `contentToJson` is coming from https://github.com/AykutSarac/jsoncrack.com/blob/main/src/lib/utils/json/jsonAdapter.ts#L34.

Notice how we are using a file from `src/lib/utils/json/jsonAdapter`. Author could have just put this in MonacoEditor, but when you are interested and making efforts to organise your code to use `lib/utils/json/jsonAdapter` to put your helpers in a different file is how you level up your coding game. Nobody will teach you this. Seasoned devs always try to put helper functions where they belong. 

```
const contentToJson = async (value: string, format = FileFormat.JSON): Promise<object> => {
  try {
    let json: object = {};

    if (format === FileFormat.JSON) json = parse(value);
    if (format === FileFormat.YAML) json = load(value) as object;
    if (format === FileFormat.XML) json = jxon.stringToJs(value);
    if (format === FileFormat.TOML) json = toml.parse(value);
    if (format === FileFormat.CSV) json = await csv2json(value);
    if (format === FileFormat.XML && keyExists(json, "parsererror")) throw Error("Unknown error!");

    if (!json) throw Error("Invalid JSON!");

    return Promise.resolve(json);
  } catch (error: any) {
    throw error;
  }
};

```

Okay, it is simple parse on contents from monaco editor since contents are json.

`if (!useConfig.getState().liveTransformEnabled && skipUpdate) return;` 

![liveTransformEnabled](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8urq2th0rcjngyxfalnv.png)

This is directly related to the `BottomBar` as shown in image, notice how zustand makes it easy to establish state communication between `Editor` component and `Bottombar`

```
if (get().hasChanges && contents && contents.length < 80_000 && !isIframe() && !isFetchURL) {
        sessionStorage.setItem("content", contents);
        sessionStorage.setItem("format", get().format);
        set({ hasChanges: true });
      }
```

So far, hasChanges is true, contents has got some data, isIframe is false and isFetchURL is false. This if block gets executed and saves the data into `sessionStorage`.
 
Finally

```
debouncedUpdateJson(json);
```

[debouncedupdateJson](https://github.com/AykutSarac/jsoncrack.com/blob/main/src/store/useFile.ts#L71) has the following code:

```
const debouncedUpdateJson = debounce((value: unknown) => {
  useGraph.getState().setLoading(true);
  useJson.getState().setJson(JSON.stringify(value, null, 2));
}, 800);
```
It is a super common pattern to use debounce when you are dealing with user input and to save the data. Read more about it [here](https://www.npmjs.com/package/lodash.debounce) 

`useJson.getState().setJson` deserves a new article 4.2.1 because of the following code snippets and the side effects involved with useGraph

```
const useJson = create<JsonStates & JsonActions>()((set, get) => ({
  ...initialStates,
  getJson: () => get().json,
  setJson: json => {
    set({ json, loading: false });
    useGraph.getState().setGraph(json);
  },
  clear: () => {
    set({ json: "", loading: false });
    useGraph.getState().clearGraph();
  },
}));
```
The above snippet is from [here](https://github.com/AykutSarac/jsoncrack.com/blob/main/src/store/useJson.ts#L20)

Once we understand, useJson and useGraph, it lays a foundation to further explore different use cases involved with yaml, xml, csv formats as well.

## Conclusion:
We looked at how you need to be specific about what you are looking for, especially when you have lot of unknowns, do not let this scare you away, I keep saying this, you have to comfortable with not knowing enough YET. We looked at props used in MonacoEditor, a lot of the props were straight forward. Because of the complexity and side effects involved in `debouncedJson`'s useGraph and useJson, we decided to make a new article 4.2.1.1 talking in depth about `useJson.getState().setJson` and the side effect involving `setGraph`. Thank you for reading till the end. If you have any questions or need help with a project or looking for an IT partner for your business, feel free to reach out to us at ram@tthroo.com