Ashel
Ashel

Reputation: 21

Best way to manage my application state, useState vs useReducer?

I've been using React for some time but know I want to improve my knowledge to another level so I want to know what would be your approach for managing the state on this kind of application?

import { Pedalboard } from "../../Components/Pedalboard/Pedalboard";
import { PedalboardOptions } from "../../Components/PedalboardOptions/PedalboardOptions";
import { exampleData } from "./exampleData";
import { Style } from "./PedalboardView.css";
import { useWindowSize } from "../../Hooks";
import { DndProvider } from "react-dnd";
import MultiBackend from "react-dnd-multi-backend";
import HTML5toTouch from "react-dnd-multi-backend/dist/esm/HTML5toTouch";

export const PedalboardView = () => {
  let windowSize = useWindowSize();
  const bodyRef = useRef();
  const [pedalboardData, setPedalboardData] = useState(
    JSON.parse(localStorage.getItem("pedalboardData"))
      ? JSON.parse(localStorage.getItem("pedalboardData"))
      : exampleData
  );
  const [scale, setScale] = useState(
    JSON.parse(localStorage.getItem("scale"))
      ? JSON.parse(localStorage.getItem("scale"))
      : 18
  );
  const [pbAreaSize, setPbAreaSize] = useState(
    JSON.parse(localStorage.getItem("pbAreaSize"))
      ? JSON.parse(localStorage.getItem("pbAreaSize"))
      : { width: 60, height: 30 }
  );
  const [htmlDrag, setHtmlDrag] = useState(true);
  //Temporary options
  const [fitToWidth, setFitToWidth] = useState(false);
  const [fitToHeight, setFitToHeight] = useState(false);
  const [hideOptions, setHideOptions] = useState(false);
  const [showTransitions, setShowTransitions] = useState(false);
  const [autofillEmpty, setAutofillEmpty] = useState(false);
  const [unitFactor, setUnitFactor] = useState("1");
  const [pbScrollBarSize, setPbScrollBarSize] = useState({
    width: 0,
    height: 0,
  });
  const actualElement = useRef();

  //We send the available dimensions to the child from here to have it before in case we need
  //to calculate percentages and animations
  let availableWidth =
    windowSize !== undefined
      ? hideOptions
        ? windowSize.width
        : windowSize.width * 0.8
      : "";
  let availableHeight = windowSize !== undefined ? windowSize.height - 50 : "";

  const preSetScale = (newScale) => {
    // When the scale changes the elements positions are recalculated
    let aux2 = { ...pedalboardData };
    Object.keys(pedalboardData).map((key) => {
      aux2[key].left = (aux2[key].left * newScale) / scale;
      aux2[key].top = (aux2[key].top * newScale) / scale;
    });
    localStorage.setItem("scale", JSON.stringify(newScale));
    setPedalboardData(aux2);
    setScale(newScale);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  };

  const fillEmptySpace = (type = "both") => {
    setPbAreaSize({
      width:
        (type === "width" || type === "both") &&
        pbAreaSize.width < availableWidth / scale
          ? availableWidth / scale - pbScrollBarSize.width / scale
          : pbAreaSize.width,
      height:
        (type === "height" || type === "both") &&
        pbAreaSize.height < availableHeight / scale
          ? availableHeight / scale - pbScrollBarSize.height / scale
          : pbAreaSize.height,
    });
  };

  //Local storage saving
  useEffect(() => {
    localStorage.setItem("pbAreaSize", JSON.stringify(pbAreaSize));
  }, [pbAreaSize]);
  useEffect(() => {
    localStorage.setItem("pedalboardData", JSON.stringify(pedalboardData));
  }, [pedalboardData]);
  useEffect(() => {
    localStorage.setItem("scale", JSON.stringify(scale));
  }, [scale]);

  //Effects
  useEffect(() => {
    if (fitToWidth) {
      preSetScale((availableWidth - pbScrollBarSize.width) / pbAreaSize.width);
    }
    if (fitToHeight) {
      preSetScale(
        (availableHeight - pbScrollBarSize.height) / pbAreaSize.height
      );
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [
    fitToWidth,
    fitToHeight,
    pbAreaSize.width,
    availableWidth,
    pbAreaSize.height,
    availableHeight,
  ]);

  useEffect(() => {
    if (autofillEmpty) {
      if (fitToWidth) {
        setPbAreaSize({
          ...pbAreaSize,
          height:
            true && pbAreaSize.height < availableHeight / scale
              ? availableHeight / scale - pbScrollBarSize.height / scale
              : pbAreaSize.height,
        });
      } else {
        setPbAreaSize({
          width:
            true && pbAreaSize.width < availableWidth / scale
              ? availableWidth / scale - pbScrollBarSize.width / scale
              : pbAreaSize.width,
          height:
            true && pbAreaSize.height < availableHeight / scale
              ? availableHeight / scale - pbScrollBarSize.height / scale
              : pbAreaSize.height,
        });
      }
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [availableWidth]);

  useEffect(() => {
    if (autofillEmpty) {
      fillEmptySpace();
    }
  }, [scale, autofillEmpty]);
  return (
    <div css={Style(hideOptions)} ref={bodyRef}>
      <div className="headSec">Head</div>
      <div className="bodySec">
        <div className="pbZone">
          <DndProvider backend={MultiBackend} options={HTML5toTouch}>
            <Pedalboard
              className={""}
              scale={scale}
              pbAreaSize={pbAreaSize}
              showTransitions={showTransitions}
              setShowTransitions={setShowTransitions}
              setPbScrollBarSize={setPbScrollBarSize}
              setPbAreaSize={setPbAreaSize}
              pedalboardData={pedalboardData}
              availableWidth={availableWidth}
              availableHeight={availableHeight}
              setPedalboardData={setPedalboardData}
              actualElement={actualElement}
              htmlDrag={htmlDrag}
              unitFactor={unitFactor}
            />
          </DndProvider>
        </div>
        <PedalboardOptions
          className={"pbOptions"}
          scale={scale}
          setScale={preSetScale}
          pbAreaSize={pbAreaSize}
          setPbAreaSize={setPbAreaSize}
          fitToWidth={fitToWidth}
          setFitToWidth={setFitToWidth}
          fitToHeight={fitToHeight}
          setFitToHeight={setFitToHeight}
          hideOptions={hideOptions}
          setHideOptions={setHideOptions}
          setShowTransitions={setShowTransitions}
          autofillEmpty={autofillEmpty}
          setAutofillEmpty={setAutofillEmpty}
          pedalboardData={pedalboardData}
          setPedalboardData={setPedalboardData}
          htmlDrag={htmlDrag}
          setHtmlDrag={setHtmlDrag}
          fillEmptySpace={fillEmptySpace}
          unitFactor={unitFactor}
          setUnitFactor={setUnitFactor}
          actualElement={actualElement}
        />
      </div>
    </div>
  );
};

I've done some research on useState vs useReducer but i really dont know what approach would be the best, some people said that is best to use useState when the properties change independent and useReducer when some action change more of one at the same time. Looking at my functions, only some of them update 2 states at the same time so I don't know if changing all of my state to useReducer would be worth it or what would be your approach to this kind of aplication?

This is preview of what im doing (a pedalboard planer for guitarists) enter image description here

Here is the source code of my project: https://github.com/ricardoaxel/pedalvisioncore/tree/master/pedalvision/src

And other files if you dont want to enter to github: Pedalboard.jsx

import React, { useRef, useEffect, useState, useCallback } from "react";
import pedals from "../../utils/pedals.json";
import pedalboards from "../../utils/pedalboards.json";
import { PBElement } from "../PBElement/PBElement";
import { getLatestPositions } from "../../utils/functions/getLatestsPositions";
import { useDrop } from "react-dnd";
import update from "immutability-helper";

export const Pedalboard = ({
  className,
  scale,
  pbAreaSize,
  showTransitions,
  setShowTransitions,
  setPbScrollBarSize,
  setPbAreaSize,
  pedalboardData,
  setPedalboardData,
  actualElement,
  htmlDrag,
  unitFactor,
}) => {
  const localRef = useRef();
  const movePedal = (direction, num) => {
    let auxPB = { ...JSON.parse(localStorage.getItem("pedalboardData")) };
    if (
      auxPB[actualElement.current.id][direction] +
        num * JSON.parse(localStorage.getItem("scale")) <
      0
    ) {
      num = 0;
    }
    let isHorizontal =
      Math.abs(actualElement.current.particularInfo.orientation) === 0 ||
      Math.abs(actualElement.current.particularInfo.orientation) === 180;
    if (
      auxPB[actualElement.current.id][direction] +
        num * JSON.parse(localStorage.getItem("scale")) +
        actualElement.current.elTypeInfo[
          direction === "top"
            ? isHorizontal
              ? "Height"
              : "Width"
            : isHorizontal
            ? "Width"
            : "Height"
        ] *
          JSON.parse(localStorage.getItem("scale")) >
      JSON.parse(localStorage.getItem("pbAreaSize"))[
        direction === "top" ? "height" : "width"
      ] *
        JSON.parse(localStorage.getItem("scale"))
    ) {
      num = 0;
    }
    auxPB[actualElement.current.id][direction] =
      auxPB[actualElement.current.id][direction] +
      num * JSON.parse(localStorage.getItem("scale"));

    setPedalboardData({ ...auxPB });
  };

  const escFunction = (event) => {
    if (actualElement.current !== undefined) {
      switch (event.key) {
        case "ArrowLeft":
          movePedal("left", -1);
          break;
        case "ArrowRight":
          movePedal("left", 1);
          break;
        case "ArrowUp":
          movePedal("top", -1);
          break;
        case "ArrowDown":
          movePedal("top", 1);
          break;
        default:
          break;
      }
    }
  };
  useEffect(() => {
    setPbScrollBarSize({
      width: localRef.current.offsetWidth - localRef.current.clientWidth,
      height: localRef.current.offsetHeight - localRef.current.clientHeight,
    });

    document.addEventListener("keydown", escFunction, false);
  }, []);

  const deletePBElement = (id) => {
    let auxPBData = { ...pedalboardData };
    delete auxPBData[id];

    setPedalboardData({ ...auxPBData });
  };

  const rotatePBElement = (id, deg) => {
    let auxPB = { ...pedalboardData };
    auxPB[id]["orientation"] =
      parseInt(auxPB[id]["orientation"]) + deg >= 360 ||
      parseInt(auxPB[id]["orientation"]) + deg <= -360
        ? 0
        : parseInt(auxPB[id]["orientation"]) + deg;

    let auxSize = {
      width: getLatestPositions(auxPB, scale, "width") / scale + 1,
      height: getLatestPositions(auxPB, scale, "height") / scale + 1,
    };
    setPbAreaSize({
      width:
        pbAreaSize.width > auxSize.width ? pbAreaSize.width : auxSize.width,
      height:
        pbAreaSize.height > auxSize.height ? pbAreaSize.height : auxSize.height,
    });
    setPedalboardData({ ...auxPB });
  };

  const updateElementLayer = (id, num) => {
    let auxPB = { ...pedalboardData };
    let newNum = parseInt(auxPB[id]["layer"]) + num;
    auxPB[id]["layer"] = newNum < 1 ? 1 : newNum > 10 ? 10 : newNum;
    setPedalboardData({ ...auxPB });
  };

  const moveBox = useCallback(
    (id, left, top, elementTypeInfo) => {
      //Validations to avoid image traspasing available area
      if (left < 0) {
        left = 0;
      }
      if (top < 0) {
        top = 0;
      }
      let isHorizontal =
        Math.abs(pedalboardData[id].orientation) === 0 ||
        Math.abs(pedalboardData[id].orientation) === 180;

      if (
        left + elementTypeInfo[isHorizontal ? "Width" : "Height"] * scale >
        pbAreaSize.width * scale
      ) {
        left =
          pbAreaSize.width * scale -
          elementTypeInfo[isHorizontal ? "Width" : "Height"] * scale;
      }
      if (
        top + elementTypeInfo[isHorizontal ? "Height" : "Width"] * scale >
        pbAreaSize.height * scale
      ) {
        top =
          pbAreaSize.height * scale -
          elementTypeInfo[isHorizontal ? "Height" : "Width"] * scale;
      }

      setPedalboardData(
        update(pedalboardData, {
          [id]: {
            $merge: { left, top },
          },
        })
      );
    },
    [pedalboardData]
  );
  const [, drop] = useDrop(
    () => ({
      accept: "box",
      drop(item, monitor) {
        const delta = monitor.getDifferenceFromInitialOffset();
        const left = Math.round(item.left + delta.x);
        const top = Math.round(item.top + delta.y);
        moveBox(item.id, left, top, item.elementTypeInfo);
        return undefined;
      },
    }),
    [moveBox]
  );

  return (
    <div
      css={Style(
        pbAreaSize.width,
        pbAreaSize.height,
        scale,
        localRef.current &&
          pbAreaSize.width * scale + 1 < localRef.current.clientWidth &&
          localRef.current &&
          pbAreaSize.height * scale + 1 <= localRef.current.clientHeight,
        unitFactor
      )}
      className={className}
      ref={localRef}
    >
      <div ref={drop} className="pedalboardAreaContainer">
        <div className="gridArea"></div>
        {Object.keys(pedalboardData).map((key) => {
          const { left, top, title } = pedalboardData[key];
          let locOtherData = pedalboardData[key];
          let elementTypeInfo;
          if (locOtherData.type === "pedals") {
            elementTypeInfo = pedals.filter(
              (pedal) =>
                pedal.Name === locOtherData.Name &&
                pedal.Brand === locOtherData.Brand
            )[0];
          } else {
            elementTypeInfo = pedalboards.filter(
              (pedal) =>
                pedal.Name === locOtherData.Name &&
                pedal.Brand === locOtherData.Brand
            )[0];
          }
          return (
            <PBElement
              key={key}
              id={key}
              left={left}
              top={top}
              hideSourceOnDrag={true}
              otherData={pedalboardData[key]}
              elementTypeInfo={elementTypeInfo}
              scale={scale}
              showTransitions={showTransitions}
              setShowTransitions={setShowTransitions}
              rotatePBElement={rotatePBElement}
              deletePBElement={deletePBElement}
              updateElementLayer={updateElementLayer}
              setActualElement={(val) => (actualElement.current = val)}
              htmlDrag={htmlDrag}
              handleEvent={moveBox}
            >
              {title}
            </PBElement>
          );
        })}
      </div>
    </div>
  );
};

PedalboardOptions.jsx

import { Style } from "./PedalboardOptions.css";
import React, { useState } from "react";
import pedals from "../../utils/pedals.json";
import pedalboards from "../../utils/pedalboards.json";
import { getLatestPositions } from "../../utils/functions/getLatestsPositions";

export const PedalboardOptions = ({
  className,
  scale,
  setScale,
  pbAreaSize,
  setPbAreaSize,
  fitToWidth,
  setFitToWidth,
  fitToHeight,
  setFitToHeight,
  hideOptions,
  setHideOptions,
  setShowTransitions,
  autofillEmpty,
  setAutofillEmpty,
  pedalboardData,
  setPedalboardData,
  htmlDrag,
  setHtmlDrag,
  fillEmptySpace,
  unitFactor,
  setUnitFactor,
}) => {
  const addElement = (elementIndex, type) => {
    let elementTypeInfo;
    if (type === "pedals") {
      elementTypeInfo = pedals[elementIndex];
    } else {
      elementTypeInfo = pedalboards[elementIndex];
    }
    let auxObj = {
      left: 0,
      top: 0,
      type: type,
      Name: elementTypeInfo.Name,
      Brand: elementTypeInfo.Brand,
      orientation: 0,
      //Obtaining the last layer
      layer: Math.max(...Object.values(pedalboardData).map((el) => el.layer)),
    };
    //This validation change the size of the actual area to work in case the element doesn't fit
    let auxPB = { ...pedalboardData };
    auxPB[Math.random().toString(16).slice(2)] = auxObj;
    let changeSize = false;
    let auxNewSize = { ...pbAreaSize };
    if (elementTypeInfo.Width * scale + 5 > pbAreaSize.width * scale) {
      auxNewSize = {
        ...auxNewSize,
        width: elementTypeInfo.Width + 10,
      };
      changeSize = true;
    }
    if (elementTypeInfo.Height * scale + 5 > pbAreaSize.height * scale) {
      auxNewSize = {
        ...auxNewSize,
        height: elementTypeInfo.Height + 10,
      };
      changeSize = true;
    }
    if (changeSize) {
      setPbAreaSize(auxNewSize);
    }
    setPedalboardData(auxPB);
  };

  const changeLayoutSize = (value, type) => {
    let maxOfType = getLatestPositions(pedalboardData, scale, type);
    if (value > maxOfType / scale) {
      setPbAreaSize({ ...pbAreaSize, [type]: value });
    }
  };

  const adjustLayoutToElements = (type = "both") => {
    //The use of unitFactor is to adjust to the actual type of units
    setPbAreaSize({
      width:
        type === "width" || type === "both"
          ? Math.floor(
              (getLatestPositions(pedalboardData, scale, "width") / scale + 1) *
                unitFactor
            ) / unitFactor
          : pbAreaSize.width,
      height:
        type === "height" || type === "both"
          ? Math.floor(
              (getLatestPositions(pedalboardData, scale, "height") / scale +
                1) *
                unitFactor
            ) / unitFactor
          : pbAreaSize.height,
    });
  };
  const [hideElements, setHideElements] = useState(true);
  return (
    <div
      css={Style()}
      className={className}
      onMouseEnter={() => {
        setShowTransitions(true);
        setHideElements(false);
      }}
      onClick={() => setShowTransitions(true)}
      onMouseLeave={() => setHideElements(true)}
    >
      <div className="elementsAddSection">
        <label>
          <input
            type="checkbox"
            checked={htmlDrag}
            onChange={() => setHtmlDrag(!htmlDrag)}
          />{" "}
          HTML 5 Dnd
        </label>
        <br />
        <label>
          <input
            type="checkbox"
            checked={fitToWidth}
            onChange={() => {
              setFitToWidth(!fitToWidth);
              setFitToHeight(false);
            }}
          />{" "}
          Fit to View width
        </label>
        <br />
        <label>
          <input
            type="checkbox"
            checked={fitToHeight}
            onChange={() => {
              setFitToHeight(!fitToHeight);
              setFitToWidth(false);
            }}
          />{" "}
          Fit to View height
        </label>
        <br />
        Scale (representation of inches per pixel):{scale}
        <input
          type="number"
          name="lastName"
          value={scale}
          onChange={(e) => setScale(e.target.value)}
          disabled={fitToWidth || fitToHeight}
        />
        <br />
        Units:{scale}
        <br />
        <label>
          <input
            type="checkbox"
            checked={unitFactor === "1"}
            onChange={() => setUnitFactor("1")}
          />{" "}
          in
        </label>
        <label>
          <input
            type="checkbox"
            checked={unitFactor === "2.54"}
            onChange={() => setUnitFactor("2.54")}
          />{" "}
          cm
        </label>
        <br />
        Layout size: <br />
        Adjust to last elements:
        <br />
        <button onClick={() => adjustLayoutToElements()}>Both</button>
        <button onClick={() => adjustLayoutToElements("width")}>Width</button>
        <button onClick={() => adjustLayoutToElements("height")}>Height</button>
        <br />
        Fill empty space:
        <br />
        <button onClick={() => fillEmptySpace()}>Both</button>
        <button onClick={() => fillEmptySpace("width")}>Width</button>
        <button onClick={() => fillEmptySpace("height")}>Height</button>
        <br />
        <label>
          <input
            type="checkbox"
            value={autofillEmpty}
            onChange={() => setAutofillEmpty(!autofillEmpty)}
          />{" "}
          Autofill empty space
          <br />
        </label>
        Width:
        <input
          type="number"
          name="lastName"
          value={(pbAreaSize.width * unitFactor).toFixed(2)}
          onChange={(e) => {
            changeLayoutSize(e.target.value / unitFactor, "width");
          }}
        />
        <br />
        Height:
        <input
          type="number"
          name="lastName"
          value={(pbAreaSize.height * unitFactor).toFixed(2)}
          onChange={(e) =>
            changeLayoutSize(e.target.value / unitFactor, "height")
          }
        />
        {(!hideElements || htmlDrag) && (
          <>
            <div className="elementSel">
              Pedalboards:
              <select
                onChange={(e) => addElement(e.target.value, "pedalboards")}
              >
                {pedalboards.map((pedalboard, index) => (
                  <option key={index} value={index}>
                    {pedalboard.Name}
                  </option>
                ))}
              </select>
            </div>
            <div className="elementSel">
              Pedals:
              <select onChange={(e) => addElement(e.target.value, "pedals")}>
                {pedals.map((pedal, index) => (
                  <option key={index} value={index}>
                    {pedal.Name}
                  </option>
                ))}
              </select>
            </div>
          </>
        )}
      </div>
      <div className="toggleBtn" onClick={() => setHideOptions(!hideOptions)}>
        {">"}
      </div>
    </div>
  );
};

PBElement.jsx

import { useState } from "react";
import { useDrag } from "react-dnd";
import { Style } from "./PBElement.css";
import Draggable from "react-draggable";

export const PBElement = ({
  id,
  left,
  top,
  hideSourceOnDrag,
  otherData,
  elementTypeInfo,
  scale,
  showTransitions,
  setShowTransitions,
  rotatePBElement,
  deletePBElement,
  updateElementLayer,
  setActualElement,
  htmlDrag,
  handleEvent,
}) => {
  const [hideOptions, setHideOptions] = useState(false);
  const [{ isDragging }, drag] = useDrag(
    () => ({
      type: "box",
      item: { id, left, top, elementTypeInfo },
      collect: (monitor) => ({
        isDragging: monitor.isDragging(),
      }),
    }),
    [id, left, top]
  );
  if (isDragging && hideSourceOnDrag) {
    return <div ref={drag} />;
  }

  if (htmlDrag) {
    return (
      <div
        css={Style(
          elementTypeInfo.Width,
          elementTypeInfo.Height,
          scale,
          showTransitions,
          otherData,
          hideOptions
        )}
        style={{ left: left, top: top, touchAction: "none" }}
        ref={drag}
        onMouseLeave={() => setHideOptions(false)}
        onDragStart={() => setHideOptions(true)}
        // onDragLeave={() => setHideOptions(false)}
        // onDragEnd={() => setHideOptions(false)}
        onClick={() =>
          setActualElement({
            id: id,
            particularInfo: otherData,
            elTypeInfo: elementTypeInfo,
          })
        }
      >
        <img
          className={"elementImage"}
          src={require(`../../assets/Images/${otherData.type}/${elementTypeInfo.Image}`)}
          alt=""
        />

        <div className="borderSquare" draggable="false">
          <div className={`options `}>
            <p onClick={() => rotatePBElement(id, -90)}>{"<-"}</p>
            <p onClick={() => deletePBElement(id)}>X</p>
            <p onClick={() => rotatePBElement(id, 90)}>{"->"}</p>
          </div>
        </div>
        <div className={`layer `} draggable="false">
          <p onClick={() => updateElementLayer(id, 1)}>{"A"}</p>
          <p>{otherData.layer}</p>
          <p onClick={() => updateElementLayer(id, -1)}>{"V"}</p>
        </div>
      </div>
    );
  } else {
    return (
      <Draggable
        position={{
          x: left,
          y: top,
        }}
        key={id}
        bounds="parent"
        onStop={(e, data) =>
          handleEvent(id, data.lastX, data.lastY, elementTypeInfo)
        }
        onStart={() => setShowTransitions(false)}
        // onDrag={() => setShowTransitions(false)}
        draggable="false"
      >
        <div
          css={Style(
            elementTypeInfo.Width,
            elementTypeInfo.Height,
            scale,
            showTransitions,
            otherData,
            false
          )}
          draggable="false"
          onClick={() => {
            setActualElement({
              id: id,
              particularInfo: otherData,
              elTypeInfo: elementTypeInfo,
            });
            setShowTransitions(true);
          }}
        >
          <img
            className={"elementImage"}
            src={require(`../../assets/Images/${otherData.type}/${elementTypeInfo.Image}`)}
            //To avoid the default HTML5 drag API
            draggable="false"
            alt=""
          />

          <div className="borderSquare" draggable="false">
            <div className={`options `}>
              <p onClick={() => rotatePBElement(id, -90)}>{"<-"}</p>
              <p onClick={() => deletePBElement(id)}>X</p>
              <p onClick={() => rotatePBElement(id, 90)}>{"->"}</p>
            </div>
          </div>
          <div className={`layer `} draggable="false">
            <p onClick={() => updateElementLayer(id, 1)}>{"A"}</p>
            <p>{otherData.layer}</p>
            <p onClick={() => updateElementLayer(id, -1)}>{"V"}</p>
          </div>
        </div>
      </Draggable>
    );
  }
};

Upvotes: 0

Views: 104

Answers (1)

user14018874
user14018874

Reputation:

Just for a start, take a look at these useState calls:

  const [pedalboardData, setPedalboardData] = useState(
    JSON.parse(localStorage.getItem("pedalboardData"))
      ? JSON.parse(localStorage.getItem("pedalboardData"))
      : exampleData
  );
  const [scale, setScale] = useState(
    JSON.parse(localStorage.getItem("scale"))
      ? JSON.parse(localStorage.getItem("scale"))
      : 18
  );
  const [pbAreaSize, setPbAreaSize] = useState(
    JSON.parse(localStorage.getItem("pbAreaSize"))
      ? JSON.parse(localStorage.getItem("pbAreaSize"))
      : { width: 60, height: 30 }
  );

It's obviously redundant and unnecessary so let's make this a reusable hook instead:

function useLocalStorage(key, defaultTo) {
    return useState(
        localStorage.getItem(key)
            ? JSON.parse(localStorage.getItem(key))
            : defaultTo
    );
}

And now when we use it we can see it's much cleaner:

const [pedalboardData, setPedalboardData] = useLocalStorage("pedalboardData", exampleData);

const [scale, setScale] = useLocalStorage("scale", 18);

const [pbAreaSize, setPbAreaSize] = useLocalStorage("pbAreaSize", { width: 60, height: 30 });

And another thing you might want to do is namespace your local storage keys. Keys like scale are too common and might get overriden/read by other sites.

You could try something like whatever-my-app-name-is-scale instead.

Upvotes: 1

Related Questions