콜백으로 입력한 데이터 전달하기

라용·2022년 11월 15일
0

위코드 - 스터디로그

목록 보기
100/100

위코드 기업협업에 참여하며 정리한 내용입니다.

아래 도식처럼 하위 컴포넌트에서 입력한 데이터가 위로 올라가 최종 view 에 반영해야 한다면 여러번 콜백함수를 거치게 된다. 아래 구조는 Editpanel 에 이벤트가 발생하는 버튼이 있어서, 그 하위에 EditpanelMain 에 버튼이 있어야 코드 가독성은 더 좋을 것이라는 피드백을 받았는데, 당장은 지금 구조로도 이해하기 급급하니 일단 지금 상태로 정리해둔다.

이 흐름을 어떻게 정리하면 좋을 까 생각해봤지만 정답은 없으니 일단 아래부터 차근차근 타고 올라가 본다.

InputTitle.tsx

inputTitle 은 텍스트를 입력받는 input 태그를 담은 컴포넌트다. 이 컴포넌트는 이곳 저곳에 중복으로 사용될 수 있다. 입려된 값을 콜백함수에 인자로 담아 전달해야 하므로, 프롭스로 onChange 콜백함수를 받는다. 전달해주는 곳에서는 onChange에 해당 인풋값을 관리하는 함수를 담는다. onChnage={해당 값 세팅하는 함수}

const InputTitle = ({
  defaultTitle,
  onChange, // 프롭스로 전달받은 콜백함수, 인풋태그의 onChange 와는 다른 것
}: {
  defaultTitle: string; 
  onChange: (title: string) => void; 
}) => {
  const [inputTitleValue, setInputTitleValue] = useState( // 인풋값 관리
    defaultTitle ? defaultTitle : ""
  );

  const onChangeHandler = (e: any) => { // 
    setInputTitleValue(e.target.value); // 인풋값을 state 로 관리하고
    onChange(e.target.value); // 그렇게 관리한 값을 콜백함수에 담아서 전달. 
  };

  return (
    <input
      type="text"
      placeholder="제목을 입력하세요"
      className="flex-1 text-xs text-dark_gray placeholder:text-dark_gray p-2.5 border border-middle_purple rounded-md w-full mb-2.5 "
      onChange={onChangeHandler}
      value={inputTitleValue} // value 값을 state 로 관리
    />
  );
};

EditPanelMain.tsx

위 InputTitle 은 EditPanelMain 의 자식 컴포넌트다. 여기서 만든 콜백함수를 onChange 프롭스로 전달했던 것이고 그렇게 정보를 가지고 올라온 콜백함수가 여기서 실행된다. 이때 기존 데이터인 editInfo 의 값을 바꾸어 주어야 하므로 해당 객채 값을 참조하는 별도 값을 lodash 의 _.cloneDeep 으로 복사하고, 그 복사된 객체의 값을 바꾼 후 바꾼 객체를 데이터를 업세이트하는 함수로 전달한다. 다시 콜백이 이루어진다. updateEditInfo가 다시 정보를 가지고 올라간다.

interface functionProps {
  editInfo: any;
  updateEditInfo: (item: any) => void;
}

const EditPanelMain = ({ editInfo, updateEditInfo }: functionProps) => {
  const onChangeMainTitle = (title: string) => { // 콜백함수로 전달받은 인풋값이 여기 title 로 입력
    const newEditInfo = _.cloneDeep(editInfo); // 객체 복사
    newEditInfo.main_section.main_title = title; // 입력된 값 반영해서 수정하고
    updateEditInfo(newEditInfo); // 바뀐 객체 정보를 담아서 콜백으로 올림
  };

  return (
    <div>
      ...
      <div className="flex">
        <div className="flex-1 mb-3">
          <InputTitle defaultTitle="" onChange={onChangeMainTitle} /> // 여기서 프롭스 전달한 것
          <InputColor />
        </div>
      </div>
    </div>
  );
};

EditPanel.tsx

EditPanel 은 EditPanelMain 의 부모 컴포넌트다. 아래에서는 focusId 에 따라 panel 컴포넌트를 선택해서 뿌려주는데 EditPanelMain 에 editInfo 와 updateEditInfo 가 프롭스로 전달된다. editInfo의 초기값에 기존 데이터 형식이 담겨있으니 이 값의 이름을 잘 확인해서 교체를 해주어야 한다. EditPanel 에는 이벤트가 발생하는 버튼이 있는데, 이 버튼을 누르면 그렇게 바뀐 입력값이 View 라는 부모 컴포넌트에 반영되어야 한다. 그래서 다시 한번 콜백으로.. 바뀐 editInfo 정보를 담아서 올려보낸다. 이 때 사용한 함수는 onApplyClickEvent 로 부모 컴포넌트에게 프롭스로 전달받은 것이다.

const EditPanel = ({ focusId, onApplyClickEvent }: ClickEventProps) => {
  const [editInfo, setEditInfo] = useState<any>({
    .. 
    main_section: {
      main_title: "위코드 벳플릭스",
      main_title_color: "#eb0909",
      sub_title: "글자를 바꾼다면",
      sub_title_color: "#0900b5",
      bg_image_url:
        "https://images.unsplash.com/photo-1561037404-61cd46aa615b?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2070&q=80",
    },
  });

  const updateEditInfo = (updateInfo: any) => {
    setEditInfo(updateInfo);
  };

  const EDIT_PANEL = [
    { id: "nav", panels: EditPanelNav },
    { id: "main", panels: EditPanelMain },
    { id: "imageSlide", panels: EditPanelImageSlider },
    { id: "leftTextRightImage", panels: EditPanelLeftRightImage },
    { id: "textCard", panels: EditPanelTextCard },
    { id: "introText", panels: EditPanelIntroText },
    {
      id: "leftTextRightMultiImages",
      panels: EditPanelLeftTextRightMultiImages,
    },
    { id: "textSlide", panels: EditPanelTextSlide },
    { id: "footer", panels: EditPanelFooter },
  ];

  const focusEditPanel: any = EDIT_PANEL.find((item) => item.id === focusId);

  return (
    <div className="ml-5 ">
      <div className="sticky top-8">
        <div className="w-[500px] p-10 text-sm bg-white shadow-md ">
          {focusId.length !== 0 ? (
            <focusEditPanel.panels 
              editInfo={editInfo}
              updateEditInfo={updateEditInfo}
            />
          ) : (
            <EditPanelDefault />
          )}
        </div>

        <button
          className="w-[500px] mt-5 p-3 bg-main_purple text-white text-sm shadow-md rounded"
          onClick={() => {
            if (editInfo) {
              onApplyClickEvent(editInfo); // 정제한 데이터 콜백으로 다시 던져주기
            }
          }}
        >
          적용하기
        </button>
      </div>
    </div>
  );
};

View.tsx

이제 View.txs 까지 올라와서 onApplyClickEvent 가 매개변수로 받은 데이터로 실행되면 되는데, 이때 전달받은 데이터의 id 값으로 구분이 되면 chagedValue.id 를 비교해 데이터를 수정해주는 것이 더 정확한 방식일 것 같다. 하지만,, 일단은 목으로 했던 것이가 해당 Id 값이 없었고, 선택된 것은 focusId 기준으로 데이터를 수정했다. 이 부분은 그냥 봐도 외부 값을 기준으로 조건을 걸어서 이상한 느낌이라 수정이 필요하다. 아무튼 그렇게 정제한 데이터를 기존 data 반영했고, useEffect 로 data 가 변경될 때마다 mainData 가 수정되도록 세팅했다.

const View = () => {
  const [focusId, setFocusId] = useState("");
  const [data, setData] = useState<any>(null);
  const [mainData, setMainData] = useState<MainData>();
  ..

  const onApplyClickEvent = (changedValue: any) => {
    const newValue = _.cloneDeep(data);
    const mainChangeValue = changedValue.main_section;
    
    if (focusId == "main") {
      newValue.main_section.main_title = mainChangeValue.main_title;
      newValue.main_section.main_title_color = mainChangeValue.main_title_color;
      newValue.main_section.sub_title = mainChangeValue.sub_title;
      newValue.main_section.sub_title_color = mainChangeValue.sub_title_color;
      newValue.main_section.bg_image_url = mainChangeValue.bg_image_url;
      setData(newValue);
    }

    // if (changedValue.id === "main") {
    //   const value = changedValue as MainData;
    //   if (data) {
    //     newValue.main.main_title = value.main_title;
    //     newValue.main.main_title_color = value.main_title_color;
    //     newValue.main.sub_title = value.sub_title;
    //     newValue.main.sub_title_color = value.sub_title_color;
    //     setData(newValue);
    //   }
    // }
  };

  useEffect(() => {
    //json 데이터 변경시 처리해줘야할것들
    //파싱해서 각각 state 분리해주거나
    if (data) {
      ..
      setMainData(data.main_section); // 메인 데이터 저장
      ..
    }
  }, [data]);

  useEffect(() => { // 최초 목데이터 로딩
    fetch("/DEFAULT_DATA.json")
      .then((response) => {
        return response.json();
      })
      .then((data) => {
        setData(data); // 데이터 저장
      });
  }, []);

  return (
    <div className="flex justify-center pb-20 pt-10">
      <div className="mr-10">
        ..
        <div className="shadow-md cursor-pointer w-[800px]">
          ..
          <ViewMain
            data={mainData}
            focusId={focusId}
            sectionClick={sectionClick}
          />
          ..
        </div>
      </div>
      <EditPanel focusId={focusId} onApplyClickEvent={onApplyClickEvent} /> // 요기서 프롭으로 전달
    </div>
  );
};

이렇게 적어보니 한번더 정리가 되긴 하는데, 아무래도 처음부터 다시 구조를 잡고 작성을 해야 할 것 같다.

profile
Today I Learned

0개의 댓글