import React from "react";
import ReactDOM from "react-dom";
import { useUnit } from "effector-react";
import { createEvent, createEffect, createStore, sample } from "effector";
const folderNodeAdded = createEvent<{ path: string; newNode: Node }>();
const folderNodeDeleted = createEvent<string>();
const folderToggled = createEvent<string>();
const getFoldersFx = createEffect(async () => {
const url = 'https://gist.githubusercontent.com/raw/' + 'd961d7eba48d4319debf57306f72cd63'
const req = await fetch(url)
const res = await req.json()
const addNodeFx = createEffect(
const updatedTree = JSON.parse(JSON.stringify(folderTree));
addNode(updatedTree, path, newNode);
const deleteNodeFx = createEffect(
({ folderTree, path }: { folderTree: Node; path: string }) => {
const updatedTree = JSON.parse(JSON.stringify(folderTree));
deleteNode(updatedTree, path);
const toggleFolderFx = createEffect(
openFolders: Record<string, boolean>;
[path]: !openFolders[path],
const $folderTree = createStore<Node>({}).on(
const $openFolders = createStore<Record<string, boolean>>({});
fn: (folderTree, { path, newNode }) => ({ folderTree, path, newNode }),
clock: folderNodeDeleted,
fn: (folderTree, path) => ({ folderTree, path }),
fn: (openFolders, path) => ({ openFolders, path }),
$folderTree.on(addNodeFx.doneData, (_, updatedTree) => updatedTree);
$folderTree.on(deleteNodeFx.doneData, (_, updatedTree) => updatedTree);
$openFolders.on(toggleFolderFx.doneData, (_, updatedFolders) => updatedFolders);
const findNodeAndParent = (data: Node, path: string) => {
const parts = path.split("/");
let current: Node | undefined = data;
let parent: Node | undefined = undefined;
for (const part of parts) {
current = current?.children?.find((child) => child.name === part);
return { node: undefined, parent: undefined };
return { node: current, parent };
const deleteNode = (data: Node, path: string) => {
const { node, parent } = findNodeAndParent(data, path);
parent.children = parent.children?.filter((child) => child !== node);
const addNode = (data: Node, path: string, newNode: Node) => {
data.children = data.children || [];
data.children.push(newNode);
const { node } = findNodeAndParent(data, path);
node.children = node.children || [];
node.children.push(newNode);
const FolderTree = () => {
const handleCreate = (path: string, type: string) => {
const name = prompt(`Enter the name of the new ${type}:`);
children: type === "folder" ? [] : undefined,
onFolderNodeAdded({ path, newNode });
const handleDelete = (path: string) => {
if (window.confirm("Are you sure you want to delete this item?")) {
onFolderNodeDeleted(path);
const renderTree = () => {
const stack = [{ data: folderTree, path: "" }];
const elements: React.ReactNode[] = [];
const renderFolder = (currentData: Node, path: string) => {
<div key={path} style={{ marginLeft: path !== "" && path.split("/").length * 20 }}>
onClick={() => onFolderToggled(path)}
style={{ cursor: "pointer" }}
{openFolders[path] ? "[-]" : "[+]"} {currentData.name}
<button onClick={() => handleCreate(path, "folder")}>
<button onClick={() => handleCreate(path, "file")}>
<button onClick={() => handleDelete(path)}>Delete</button>
elements.push(folderElement);
currentData.children?.forEach((child) => {
const newPath = path === "" ? child.name : `${path}/${child.name}`;
stack.push({ data: child, path: newPath });
const renderFile = (currentData: Node, path: string) => (
<div key={path} style={{ marginLeft: path.split("/").length * 20 }}>
<button onClick={() => handleDelete(path)}>Delete</button>
while (stack.length > 0) {
const { data: currentData, path } = stack.pop()!;
if (Array.isArray(currentData.children)) {
elements.push(renderFolder(currentData, path));
elements.push(renderFile(currentData, path));
const folders = renderTree();
return <div>{folders}</div>;
ReactDOM.render(<Example />, document.getElementById("root"));