Browse Source

implement zustand

AykutSarac 2 years ago
parent
commit
1c7056e8a9

+ 2 - 1
package.json

@@ -29,7 +29,8 @@
     "reaflow": "^5.0.4",
     "save-html-as-image": "^1.7.1",
     "styled-components": "^5.3.5",
-    "usehooks-ts": "^2.5.2"
+    "usehooks-ts": "^2.5.2",
+    "zustand": "^4.0.0-rc.4"
   },
   "devDependencies": {
     "@testing-library/jest-dom": "^5.16.4",

+ 3 - 3
src/components/CustomNode/ObjectNode.tsx

@@ -1,6 +1,6 @@
 import React from "react";
-import { useConfig } from "src/hocs/config";
 import { ConditionalWrapper, CustomNodeProps } from "src/components/CustomNode";
+import useConfig from "src/hooks/store/useConfig";
 import * as Styled from "./styles";
 
 const ObjectNode: React.FC<CustomNodeProps<[string, string][]>> = ({
@@ -10,11 +10,11 @@ const ObjectNode: React.FC<CustomNodeProps<[string, string][]>> = ({
   x,
   y,
 }) => {
-  const { settings } = useConfig();
+  const performance = useConfig((state) => state.settings.performance);
 
   return (
     <Styled.StyledForeignObject width={width} height={height} x={0} y={0}>
-      <ConditionalWrapper condition={settings.performance}>
+      <ConditionalWrapper condition={performance}>
         <Styled.StyledText width={width} height={height}>
           {value.map(
             (val, idx) =>

+ 3 - 3
src/components/CustomNode/TextNode.tsx

@@ -1,6 +1,6 @@
 import React from "react";
-import { useConfig } from "src/hocs/config";
 import { ConditionalWrapper, CustomNodeProps } from "src/components/CustomNode";
+import useConfig from "src/hooks/store/useConfig";
 import * as Styled from "./styles";
 
 const TextNode: React.FC<CustomNodeProps<string>> = ({
@@ -11,11 +11,11 @@ const TextNode: React.FC<CustomNodeProps<string>> = ({
   x,
   y,
 }) => {
-  const { settings } = useConfig();
+  const performance = useConfig((state) => state.settings.performance);
 
   return (
     <Styled.StyledForeignObject width={width} height={height} x={0} y={0}>
-      <ConditionalWrapper condition={settings.performance}>
+      <ConditionalWrapper condition={performance}>
         <Styled.StyledText width={width} height={height}>
           <Styled.StyledKey
             data-x={x}

+ 12 - 6
src/components/Graph/index.tsx

@@ -1,11 +1,17 @@
 import React from "react";
 import { Canvas, EdgeData, ElkRoot, NodeData } from "reaflow";
 import { CustomNode } from "src/components/CustomNode";
-import { useConfig } from "src/hocs/config";
 import { getEdgeNodes } from "src/containers/LiveEditor/helpers";
+import useConfig from "src/hooks/store/useConfig";
+import shallow from "zustand/shallow";
 
 export const Graph: React.FC = () => {
-  const { json, settings } = useConfig();
+  const json = useConfig((state) => state.json);
+  const [expand, layout] = useConfig(
+    (state) => [state.settings.expand, state.settings.layout],
+    shallow
+  );
+
   const [nodes, setNodes] = React.useState<NodeData[]>([]);
   const [edges, setEdges] = React.useState<EdgeData[]>([]);
   const [size, setSize] = React.useState({
@@ -14,11 +20,11 @@ export const Graph: React.FC = () => {
   });
 
   React.useEffect(() => {
-    const { nodes, edges } = getEdgeNodes(json, settings.expand);
+    const { nodes, edges } = getEdgeNodes(json, expand);
 
     setNodes(nodes);
     setEdges(edges);
-  }, [json, settings.expand]);
+  }, [json, expand]);
 
   const onCanvasClick = () => {
     const input = document.querySelector("input:focus") as HTMLInputElement;
@@ -36,8 +42,8 @@ export const Graph: React.FC = () => {
       edges={edges}
       maxWidth={size.width}
       maxHeight={size.height}
-      direction={settings.layout}
-      key={settings.layout}
+      direction={layout}
+      key={layout}
       onCanvasClick={onCanvasClick}
       onLayoutChange={onLayoutChange}
       node={CustomNode}

+ 21 - 17
src/components/Sidebar/index.tsx

@@ -20,13 +20,13 @@ import {
 } from "react-icons/ai";
 
 import { Tooltip } from "src/components/Tooltip";
-import { ConfigActionType } from "src/reducer/reducer";
-import { useConfig } from "src/hocs/config";
 import { useRouter } from "next/router";
 import { ImportModal } from "src/containers/ImportModal";
 import { ClearModal } from "src/containers/ClearModal";
 import { ShareModal } from "src/containers/ShareModal";
 import { IoAlertCircleSharp } from "react-icons/io5";
+import useConfig from "src/hooks/store/useConfig";
+import { getNextLayout } from "src/containers/LiveEditor/helpers";
 
 const StyledSidebar = styled.div`
   display: flex;
@@ -136,7 +136,10 @@ const StyledAlertIcon = styled(IoAlertCircleSharp)`
 `;
 
 export const Sidebar: React.FC = () => {
-  const { json, settings, dispatch } = useConfig();
+  const json = useConfig((state) => state.json);
+  const updateSetting = useConfig((state) => state.updateSetting);
+
+  const { expand, performance, layout } = useConfig((state) => state.settings);
   const router = useRouter();
   const [uploadVisible, setUploadVisible] = React.useState(false);
   const [clearVisible, setClearVisible] = React.useState(false);
@@ -152,12 +155,12 @@ export const Sidebar: React.FC = () => {
   };
 
   const toggleExpandCollapse = () => {
-    dispatch({ type: ConfigActionType.TOGGLE_EXPAND });
-    toast(`${settings.expand ? "Collapsed" : "Expanded"} nodes.`);
+    updateSetting("expand", !expand);
+    toast(`${expand ? "Collapsed" : "Expanded"} nodes.`);
   };
 
   const togglePerformance = () => {
-    const toastMsg = settings.performance
+    const toastMsg = performance
       ? "Disabled Performance Mode\nSearch Node & Save Image enabled."
       : "Enabled Performance Mode\nSearch Node & Save Image disabled.";
 
@@ -166,7 +169,12 @@ export const Sidebar: React.FC = () => {
       duration: 3000,
     });
 
-    dispatch({ type: ConfigActionType.TOGGLE_PERFORMANCE });
+    updateSetting("performance", !performance);
+  };
+
+  const toggleLayout = () => {
+    const nextLayout = getNextLayout(layout);
+    updateSetting("layout", nextLayout);
   };
 
   return (
@@ -186,29 +194,25 @@ export const Sidebar: React.FC = () => {
           </StyledElement>
         </Tooltip>
         <Tooltip title="Rotate Layout">
-          <StyledElement
-            onClick={() => dispatch({ type: ConfigActionType.TOGGLE_LAYOUT })}
-          >
-            <StyledFlowIcon rotate={rotateLayout(settings.layout)} />
+          <StyledElement onClick={toggleLayout}>
+            <StyledFlowIcon rotate={rotateLayout(layout)} />
           </StyledElement>
         </Tooltip>
-        <Tooltip title={settings.expand ? "Shrink Nodes" : "Expand Nodes"}>
+        <Tooltip title={expand ? "Shrink Nodes" : "Expand Nodes"}>
           <StyledElement
             title="Toggle Expand/Collapse"
             onClick={toggleExpandCollapse}
           >
-            {settings.expand ? <CgArrowsMergeAltH /> : <CgArrowsShrinkH />}
+            {expand ? <CgArrowsMergeAltH /> : <CgArrowsShrinkH />}
           </StyledElement>
         </Tooltip>
         <Tooltip
           title={`${
-            settings.performance ? "Disable" : "Enable"
+            performance ? "Disable" : "Enable"
           } Performance Mode (Beta)`}
         >
           <StyledElement onClick={togglePerformance} beta>
-            <CgPerformance
-              color={settings.performance ? "#0073FF" : undefined}
-            />
+            <CgPerformance color={performance ? "#0073FF" : undefined} />
           </StyledElement>
         </Tooltip>
         <Tooltip title="Save JSON">

+ 3 - 4
src/containers/ClearModal/index.tsx

@@ -2,14 +2,13 @@ import React from "react";
 import toast from "react-hot-toast";
 import { Button } from "src/components/Button";
 import { Modal, ModalProps } from "src/components/Modal";
-import { useConfig } from "src/hocs/config";
-import { ConfigActionType } from "src/reducer/reducer";
+import useConfig from "src/hooks/store/useConfig";
 
 export const ClearModal: React.FC<ModalProps> = ({ visible, setVisible }) => {
-  const { dispatch } = useConfig();
+  const updateJson = useConfig((state) => state.updateJson);
 
   const handleClear = () => {
-    dispatch({ type: ConfigActionType.SET_JSON, payload: "{}" });
+    updateJson("{}");
     toast.success(`Cleared JSON and removed from memory.`);
     setVisible(false);
   };

+ 3 - 3
src/containers/Editor/Panes.tsx

@@ -1,16 +1,16 @@
 import { Allotment } from "allotment";
 import React from "react";
-import { useConfig } from "src/hocs/config";
 import { JsonEditor } from "src/containers/JsonEditor";
 import { StyledEditor } from "./styles";
 import dynamic from "next/dynamic";
+import useConfig from "src/hooks/store/useConfig";
 
 const LiveEditor = dynamic(() => import("src/containers/LiveEditor"), {
   ssr: false,
 });
 
 const Panes: React.FC = () => {
-  const { settings } = useConfig();
+  const hideEditor = useConfig((state) => state.settings.hideEditor);
 
   return (
     <StyledEditor>
@@ -18,7 +18,7 @@ const Panes: React.FC = () => {
         preferredSize={400}
         minSize={300}
         maxSize={600}
-        visible={!settings.hideEditor}
+        visible={!hideEditor}
       >
         <JsonEditor />
       </Allotment.Pane>

+ 19 - 14
src/containers/Editor/Tools.tsx

@@ -9,9 +9,9 @@ import { FiDownload } from "react-icons/fi";
 import { HiOutlineSun, HiOutlineMoon } from "react-icons/hi";
 import { MdCenterFocusWeak } from "react-icons/md";
 import { SearchInput } from "src/containers/SearchInput";
-import { useConfig } from "src/hocs/config";
-import { ConfigActionType } from "src/reducer/reducer";
 import styled from "styled-components";
+import useConfig from "src/hooks/store/useConfig";
+import shallow from "zustand/shallow";
 
 export const StyledTools = styled.div`
   display: flex;
@@ -41,17 +41,22 @@ const StyledToolElement = styled.button`
 `;
 
 export const Tools: React.FC = () => {
-  const { settings, dispatch } = useConfig();
-
-  const zoomIn = () => dispatch({ type: ConfigActionType.ZOOM_IN });
-
-  const zoomOut = () => dispatch({ type: ConfigActionType.ZOOM_OUT });
-
-  const centerView = () => dispatch({ type: ConfigActionType.CENTER_VIEW });
+  const [lightmode, performance, hideEditor] = useConfig(
+    (state) => [
+      state.settings.lightmode,
+      state.settings.performance,
+      state.settings.hideEditor,
+    ],
+    shallow
+  );
 
-  const toggleEditor = () => dispatch({ type: ConfigActionType.TOGGLE_DOCK });
+  const updateSetting = useConfig((state) => state.updateSetting);
 
-  const toggleTheme = () => dispatch({ type: ConfigActionType.TOGGLE_THEME });
+  const zoomIn = useConfig((state) => state.zoomIn);
+  const zoomOut = useConfig((state) => state.zoomOut);
+  const centerView = useConfig((state) => state.centerView);
+  const toggleEditor = () => updateSetting("hideEditor", !hideEditor);
+  const toggleTheme = () => updateSetting("lightmode", !lightmode);
 
   const exportAsImage = () => {
     saveAsPng(document.querySelector("svg[id*='ref']"), {
@@ -66,10 +71,10 @@ export const Tools: React.FC = () => {
         <AiOutlineFullscreen />
       </StyledToolElement>
       <StyledToolElement aria-label="switch theme" onClick={toggleTheme}>
-        {settings.lightmode ? <HiOutlineMoon /> : <HiOutlineSun />}
+        {lightmode ? <HiOutlineMoon /> : <HiOutlineSun />}
       </StyledToolElement>
-      {!settings.performance && <SearchInput />}
-      {!settings.performance && (
+      {!performance && <SearchInput />}
+      {!performance && (
         <StyledToolElement aria-label="save" onClick={exportAsImage}>
           <FiDownload />
         </StyledToolElement>

+ 4 - 13
src/containers/ImportModal/index.tsx

@@ -2,11 +2,10 @@ import React from "react";
 import styled from "styled-components";
 import toast from "react-hot-toast";
 
-import { useConfig } from "src/hocs/config";
-import { ConfigActionType } from "src/reducer/reducer";
 import { Modal, ModalProps } from "src/components/Modal";
 import { Button } from "src/components/Button";
 import { AiOutlineUpload } from "react-icons/ai";
+import useConfig from "src/hooks/store/useConfig";
 
 const StyledInput = styled.input`
   background: ${({ theme }) => theme.BACKGROUND_TERTIARY};
@@ -55,7 +54,7 @@ const StyledUploadMessage = styled.h3`
 `;
 
 export const ImportModal: React.FC<ModalProps> = ({ visible, setVisible }) => {
-  const { dispatch } = useConfig();
+  const updateJson = useConfig((state) => state.updateJson);
   const [url, setURL] = React.useState("");
   const [jsonFile, setJsonFile] = React.useState<File | null>(null);
 
@@ -71,11 +70,7 @@ export const ImportModal: React.FC<ModalProps> = ({ visible, setVisible }) => {
       return fetch(url)
         .then((res) => res.json())
         .then((json) => {
-          dispatch({
-            type: ConfigActionType.SET_JSON,
-            payload: JSON.stringify(json),
-          });
-
+          updateJson(JSON.stringify(json));
           setVisible(false);
         })
         .catch(() => toast.error("Failed to fetch JSON!"))
@@ -87,11 +82,7 @@ export const ImportModal: React.FC<ModalProps> = ({ visible, setVisible }) => {
 
       reader.readAsText(jsonFile, "UTF-8");
       reader.onload = function (data) {
-        dispatch({
-          type: ConfigActionType.SET_JSON,
-          payload: data.target?.result as string,
-        });
-
+        updateJson(data.target?.result as string);
         setVisible(false);
       };
     }

+ 10 - 8
src/containers/JsonEditor/index.tsx

@@ -3,10 +3,9 @@ import Editor from "@monaco-editor/react";
 import parseJson from "parse-json";
 import styled from "styled-components";
 import { ErrorContainer } from "src/components/ErrorContainer/ErrorContainer";
-import { ConfigActionType } from "src/reducer/reducer";
-import { useConfig } from "src/hocs/config";
 import { Loading } from "src/components/Loading";
 import { loader } from "@monaco-editor/react";
+import useConfig from "src/hooks/store/useConfig";
 
 loader.config({ paths: { vs: "/monaco-editor/min/vs" } });
 
@@ -33,7 +32,10 @@ const StyledWrapper = styled.div`
 `;
 
 export const JsonEditor: React.FC = () => {
-  const { json, settings, dispatch } = useConfig();
+  const json = useConfig((state) => state.json);
+  const lightmode = useConfig((state) => state.settings.lightmode);
+  const updateJson = useConfig((state) => state.updateJson);
+
   const [value, setValue] = React.useState("");
   const [error, setError] = React.useState({
     message: "",
@@ -41,8 +43,8 @@ export const JsonEditor: React.FC = () => {
   });
 
   const editorTheme = React.useMemo(
-    () => (settings.lightmode ? "light" : "vs-dark"),
-    [settings.lightmode]
+    () => (lightmode ? "light" : "vs-dark"),
+    [lightmode]
   );
 
   React.useEffect(() => {
@@ -54,11 +56,11 @@ export const JsonEditor: React.FC = () => {
       try {
         if (!value) {
           setError((err) => ({ ...err, message: "" }));
-          return dispatch({ type: ConfigActionType.SET_JSON, payload: "[]" });
+          return updateJson("[]");
         }
 
         parseJson(value);
-        dispatch({ type: ConfigActionType.SET_JSON, payload: value });
+        updateJson(value);
         setError((err) => ({ ...err, message: "" }));
       } catch (jsonError: any) {
         setError((err) => ({ ...err, message: jsonError.message }));
@@ -66,7 +68,7 @@ export const JsonEditor: React.FC = () => {
     }, 1500);
 
     return () => clearTimeout(formatTimer);
-  }, [value, dispatch]);
+  }, [value, updateJson]);
 
   return (
     <StyledEditorWrapper>

+ 3 - 7
src/containers/LiveEditor/index.tsx

@@ -6,10 +6,9 @@ import {
   ReactZoomPanPinchRef,
 } from "react-zoom-pan-pinch";
 
-import { useConfig } from "src/hocs/config";
 import { Tools } from "src/containers/Editor/Tools";
-import { ConfigActionType } from "src/reducer/reducer";
 import { Graph } from "src/components/Graph";
+import useConfig from "src/hooks/store/useConfig";
 
 const StyledLiveEditor = styled.div`
   position: relative;
@@ -34,13 +33,10 @@ const wheelOptions = {
 };
 
 const LiveEditor: React.FC = () => {
-  const { dispatch } = useConfig();
+  const updateSetting = useConfig((state) => state.updateSetting);
 
   const onInit = (ref: ReactZoomPanPinchRef) => {
-    dispatch({
-      type: ConfigActionType.SET_ZOOM_PAN_PICNH_REF,
-      payload: ref,
-    });
+    updateSetting("zoomPanPinch", ref);
   };
 
   return (

+ 2 - 2
src/containers/ShareModal/index.tsx

@@ -6,7 +6,7 @@ import { Modal, ModalProps } from "src/components/Modal";
 import { Button } from "src/components/Button";
 import { BiErrorAlt } from "react-icons/bi";
 import { compress } from "compress-json";
-import { useConfig } from "src/hocs/config";
+import useConfig from "src/hooks/store/useConfig";
 
 const StyledInput = styled.input`
   background: ${({ theme }) => theme.BACKGROUND_TERTIARY};
@@ -33,7 +33,7 @@ const StyledErrorWrapper = styled.div`
 `;
 
 export const ShareModal: React.FC<ModalProps> = ({ visible, setVisible }) => {
-  const { json } = useConfig();
+  const json = useConfig((state) => state.json);
   const [url, setURL] = React.useState("");
   const [_, copy] = useCopyToClipboard();
 

+ 0 - 104
src/hocs/config.tsx

@@ -1,104 +0,0 @@
-import React from "react";
-import { defaultConfig, defaultJson } from "src/constants/data";
-import {
-  ConfigActionType,
-  ReducerAction,
-  useConfigReducer,
-} from "src/reducer/reducer";
-import { ReactComponent, StorageConfig } from "src/typings/global";
-import { isValidJson } from "src/utils/isValidJson";
-import { useRouter } from "next/router";
-import { Compressed, decompress } from "compress-json";
-
-export interface AppConfig {
-  json: string;
-  settings: StorageConfig;
-}
-
-export const initialStates: AppConfig = {
-  json: JSON.stringify(defaultJson),
-  settings: defaultConfig,
-};
-
-interface Config {
-  json: string;
-  settings: StorageConfig;
-  dispatch: React.Dispatch<ReducerAction>;
-}
-
-const defaultContext: Config = {
-  ...initialStates,
-  dispatch: () => {},
-};
-
-const ConfigContext: React.Context<Config> =
-  React.createContext(defaultContext);
-
-const useConfig = () => React.useContext(ConfigContext);
-
-const WithConfig: ReactComponent = ({ children }) => {
-  const [render, setRender] = React.useState(false);
-  const [states, dispatch] = React.useReducer(useConfigReducer, initialStates);
-  const value = {
-    dispatch,
-    json: states.json,
-    settings: states.settings,
-  };
-
-  const router = useRouter();
-  const { json } = router.query;
-
-  React.useEffect(() => {
-    const jsonStored = localStorage.getItem("json");
-    const isJsonValid =
-      typeof json === "string" && isValidJson(decodeURIComponent(json));
-
-    if (isJsonValid) {
-      const jsonDecoded = decompress(JSON.parse(isJsonValid));
-      const jsonString = JSON.stringify(jsonDecoded);
-
-      dispatch({ type: ConfigActionType.SET_JSON, payload: jsonString });
-    } else if (jsonStored) {
-      dispatch({ type: ConfigActionType.SET_JSON, payload: jsonStored });
-    }
-
-    const configStored = localStorage.getItem("config");
-
-    if (configStored) {
-      dispatch({
-        type: ConfigActionType.SET_CONFIG,
-        payload: JSON.parse(configStored),
-      });
-    }
-
-    setRender(true);
-  }, [dispatch, json]);
-
-  React.useEffect(() => {
-    if (render)
-      localStorage.setItem(
-        "config",
-        JSON.stringify({
-          ...states.settings,
-          zoomPanPinch: undefined,
-          performance: undefined,
-        })
-      );
-  }, [states.settings, render]);
-
-  return (
-    <ConfigContext.Provider value={value}>{children}</ConfigContext.Provider>
-  );
-};
-
-const withConfig = <P extends object>(
-  Component: React.ComponentType<P>
-): React.FC => {
-  return (props) => (
-    <WithConfig>
-      <Component {...(props as P)} />
-    </WithConfig>
-  );
-};
-
-export { WithConfig, useConfig, ConfigContext, withConfig };

+ 54 - 0
src/hooks/store/useConfig.tsx

@@ -0,0 +1,54 @@
+import { defaultConfig, defaultJson } from "src/constants/data";
+import { StorageConfig } from "src/typings/global";
+import create from "zustand";
+
+export interface Config {
+  json: string;
+  settings: StorageConfig;
+  updateJson: (json: string) => void;
+  loadSettings: (settings: StorageConfig) => void;
+  updateSetting: (setting: keyof StorageConfig, value: any) => void;
+  zoomIn: () => void;
+  zoomOut: () => void;
+  centerView: () => void;
+}
+
+const useConfig = create<Config>((set) => ({
+  json: JSON.stringify(defaultJson),
+  settings: defaultConfig,
+  updateJson: (json: string) => set((state) => ({ ...state, json })),
+  loadSettings: (settings: StorageConfig) =>
+    set((state) => ({ ...state, settings })),
+  updateSetting: (setting: keyof StorageConfig, value: any) =>
+    set((state) => ({
+      ...state,
+      settings: { ...state.settings, [setting]: value },
+    })),
+  zoomIn: () =>
+    set((state) => {
+      state.settings.zoomPanPinch?.setTransform(
+        state.settings.zoomPanPinch?.state.positionX,
+        state.settings.zoomPanPinch?.state.positionY,
+        state.settings.zoomPanPinch?.state.scale + 0.4
+      );
+
+      return state;
+    }),
+  zoomOut: () =>
+    set((state) => {
+      state.settings.zoomPanPinch?.setTransform(
+        state.settings.zoomPanPinch?.state.positionX,
+        state.settings.zoomPanPinch?.state.positionY,
+        state.settings.zoomPanPinch?.state.scale - 0.4
+      );
+
+      return state;
+    }),
+  centerView: () =>
+    set((state) => {
+      state.settings.zoomPanPinch?.resetTransform();
+      return state;
+    }),
+}));
+
+export default useConfig;

+ 11 - 11
src/hooks/useFocusNode.tsx

@@ -1,11 +1,11 @@
 import React from "react";
-import { useConfig } from "src/hocs/config";
 
 import {
   searchQuery,
   cleanupHighlight,
   highlightMatchedNodes,
 } from "src/utils/search";
+import useConfig from "./store/useConfig";
 
 export const useFocusNode = () => {
   const [selectedNode, setSelectedNode] = React.useState(0);
@@ -14,7 +14,7 @@ export const useFocusNode = () => {
     debounced: "",
   });
 
-  const { settings } = useConfig();
+  const zoomPanPinch = useConfig((state) => state.settings.zoomPanPinch);
 
   const skip = () => setSelectedNode((current) => current + 1);
 
@@ -27,8 +27,8 @@ export const useFocusNode = () => {
   }, [content.value]);
 
   React.useEffect(() => {
-    if (!settings.zoomPanPinch) return;
-    const zoomPanPinch = settings.zoomPanPinch.instance.wrapperComponent;
+    if (!zoomPanPinch) return;
+    const ref = zoomPanPinch.instance.wrapperComponent;
 
     const matchedNodes: NodeListOf<Element> = searchQuery(
       `span[data-key*='${content.debounced}' i]`
@@ -37,23 +37,23 @@ export const useFocusNode = () => {
 
     cleanupHighlight();
 
-    if (zoomPanPinch && matchedNode && matchedNode.parentElement) {
+    if (ref && matchedNode && matchedNode.parentElement) {
       const newScale = 1;
       const x = Number(matchedNode.getAttribute("data-x"));
       const y = Number(matchedNode.getAttribute("data-y"));
 
       const newPositionX =
-        (zoomPanPinch.offsetLeft - x) * newScale +
-        zoomPanPinch.clientWidth / 2 -
+        (ref.offsetLeft - x) * newScale +
+        ref.clientWidth / 2 -
         matchedNode.getBoundingClientRect().width / 2;
       const newPositionY =
-        (zoomPanPinch.offsetLeft - y) * newScale +
-        zoomPanPinch.clientHeight / 2 -
+        (ref.offsetLeft - y) * newScale +
+        ref.clientHeight / 2 -
         matchedNode.getBoundingClientRect().height / 2;
 
       highlightMatchedNodes(matchedNodes, selectedNode);
 
-      settings.zoomPanPinch?.setTransform(newPositionX, newPositionY, newScale);
+      zoomPanPinch?.setTransform(newPositionX, newPositionY, newScale);
     } else {
       setSelectedNode(0);
     }
@@ -61,7 +61,7 @@ export const useFocusNode = () => {
     return () => {
       if (!content.value) setSelectedNode(0);
     };
-  }, [content.debounced, settings.zoomPanPinch, selectedNode, setSelectedNode]);
+  }, [content.debounced, zoomPanPinch, selectedNode, setSelectedNode]);
 
   return [content, setContent, skip] as const;
 };

+ 4 - 4
src/pages/_app.tsx

@@ -6,8 +6,8 @@ import { init } from "@sentry/nextjs";
 
 import GlobalStyle from "src/constants/globalStyle";
 import { darkTheme, lightTheme } from "src/constants/theme";
-import { useConfig, withConfig } from "src/hocs/config";
 import { GoogleAnalytics } from "src/components/GoogleAnalytics";
+import useConfig from "src/hooks/store/useConfig";
 
 if (process.env.NODE_ENV !== "development") {
   init({
@@ -17,7 +17,7 @@ if (process.env.NODE_ENV !== "development") {
 }
 
 function JsonVisio({ Component, pageProps }: AppProps) {
-  const { settings } = useConfig();
+  const lightmode = useConfig((state) => state.settings.lightmode);
 
   React.useEffect(() => {
     if (!window.matchMedia("(display-mode: standalone)").matches) {
@@ -37,7 +37,7 @@ function JsonVisio({ Component, pageProps }: AppProps) {
   return (
     <>
       <GoogleAnalytics />
-      <ThemeProvider theme={settings.lightmode ? lightTheme : darkTheme}>
+      <ThemeProvider theme={lightmode ? lightTheme : darkTheme}>
         <GlobalStyle />
         <Component {...pageProps} />
         <Toaster
@@ -54,4 +54,4 @@ function JsonVisio({ Component, pageProps }: AppProps) {
   );
 }
 
-export default withConfig(JsonVisio);
+export default JsonVisio;

+ 0 - 118
src/reducer/reducer.ts

@@ -1,118 +0,0 @@
-import React from "react";
-import { getNextLayout } from "src/containers/LiveEditor/helpers";
-import { AppConfig, initialStates } from "../hocs/config";
-
-export enum ConfigActionType {
-  SET_CONFIG,
-  TOGGLE_LAYOUT,
-  TOGGLE_EXPAND,
-  TOGGLE_PERFORMANCE,
-  TOGGLE_DOCK,
-  TOGGLE_THEME,
-  ZOOM_IN,
-  ZOOM_OUT,
-  CENTER_VIEW,
-  SET_JSON,
-  SET_ZOOM_PAN_PICNH_REF,
-}
-
-export type ReducerAction = {
-  type: ConfigActionType;
-  payload?: any;
-};
-
-export const useConfigReducer: React.Reducer<AppConfig, ReducerAction> = (
-  state = initialStates,
-  action
-) => {
-  switch (action.type) {
-    case ConfigActionType.SET_CONFIG:
-      return {
-        ...state,
-        settings: action.payload,
-      };
-
-    case ConfigActionType.TOGGLE_THEME:
-      return {
-        ...state,
-        settings: {
-          ...state.settings,
-          lightmode: !state.settings.lightmode,
-        },
-      };
-
-    case ConfigActionType.SET_ZOOM_PAN_PICNH_REF:
-      return {
-        ...state,
-        settings: {
-          ...state.settings,
-          zoomPanPinch: action.payload,
-        },
-      };
-
-    case ConfigActionType.CENTER_VIEW:
-      state.settings.zoomPanPinch?.resetTransform();
-      return state;
-
-    case ConfigActionType.ZOOM_IN:
-      state.settings.zoomPanPinch?.setTransform(
-        state.settings.zoomPanPinch?.state.positionX,
-        state.settings.zoomPanPinch?.state.positionY,
-        state.settings.zoomPanPinch?.state.scale + 0.4
-      );
-      return state;
-
-    case ConfigActionType.ZOOM_OUT:
-      state.settings.zoomPanPinch?.setTransform(
-        state.settings.zoomPanPinch?.state.positionX,
-        state.settings.zoomPanPinch?.state.positionY,
-        state.settings.zoomPanPinch?.state.scale - 0.4
-      );
-      return state;
-
-    case ConfigActionType.TOGGLE_DOCK:
-      return {
-        ...state,
-        settings: {
-          ...state.settings,
-          hideEditor: !state.settings.hideEditor,
-        },
-      };
-
-    case ConfigActionType.TOGGLE_EXPAND:
-      return {
-        ...state,
-        settings: {
-          ...state.settings,
-          expand: !state.settings.expand,
-        },
-      };
-
-    case ConfigActionType.TOGGLE_PERFORMANCE:
-      return {
-        ...state,
-        settings: {
-          ...state.settings,
-          performance: !state.settings.performance,
-        },
-      };
-
-    case ConfigActionType.TOGGLE_LAYOUT:
-      return {
-        ...state,
-        settings: {
-          ...state.settings,
-          layout: getNextLayout(state.settings.layout),
-        },
-      };
-
-    case ConfigActionType.SET_JSON:
-      return {
-        ...state,
-        json: action.payload,
-      };
-
-    default:
-      return state;
-  }
-};

+ 12 - 0
yarn.lock

@@ -6617,6 +6617,11 @@ use-resize-observer@^9.0.0:
   dependencies:
     "@juggle/resize-observer" "^3.3.1"
 
[email protected]:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"
+  integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==
+
 usehooks-ts@^2.5.2:
   version "2.6.0"
   resolved "https://registry.yarnpkg.com/usehooks-ts/-/usehooks-ts-2.6.0.tgz#aebab367da2350a0bee1c3749bc6dd4bcce3eaae"
@@ -7003,3 +7008,10 @@ [email protected]:
   version "3.1.1"
   resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
   integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==
+
+zustand@^4.0.0-rc.4:
+  version "4.0.0-rc.4"
+  resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.0.0-rc.4.tgz#ed0e0f1fa3e1c7d0d9021739d862d88048d845da"
+  integrity sha512-BP35Rq40GBTKKtYyjLuZogzXGh289xqO8U8ivGIK43nRqURD2dEEImkUci1/jWRUz7J1OPJkUZuNySCho801gQ==
+  dependencies:
+    use-sync-external-store "1.2.0"