CKEditor & stale closure

:D ·2022년 3월 26일
2

Error 🤯

목록 보기
2/4

2일동안 나를 힘들게했던 오류와 내가 어떻게 그 오류를 해결했는지에 대해 작성해보려고한다.

실제 배포 되는 사이트라 코드를 재구성했다. 이점 유의해서 봐주시길 바란다.

import { React, useState } from "react";
import { CKEditor } from "@ckeditor/ckeditor5-react";
import ClassicEditor from "@ckeditor/ckeditor5-build-classic";

function StudyCreate() {
  const [studyState, setStudyState] = useState({
    contents: "",
    editor: "",
    files: [],
  });

  function uploadAdapter(loader) {
    return {
      upload: () => {
        return new Promise((resolve, reject) => {
          loader.file.then((file) => {
            console.log(studyState);
            setStudyState({
              ...studyState,
              files: [...studyState.files, file],
            });
          });
        });
      },
      abort: () => {
        return new Promise((resolve, reject) => {});
      },
    };
  }

  function uploadPlugin(editor) {
    editor.plugins.get("FileRepository").createUploadAdapter = (loader) => {
      return uploadAdapter(loader);
    };
  }

  return (
    <CKEditor
      name="contents"
      editor={ClassicEditor}
      data=""
      onInit={(editor) => {
        console.log("Editor is ready to use!", editor);
      }}
      config={{
        extraPlugins: [uploadPlugin],
      }}
      onChange={(event, editor) => {
        const data = editor.getData();
        setStudyState({
          ...studyState,
          contents: data,
        });
      }}
    />
  );
}

export default StudyCreate;

🤯 오류상황에 대한 설명

이것은 CKEditor 텍스트 에디터를 개발하는 코드이다.
일단 간단한 메커니즘을 설명해보면
글을 작성할 때 onChange에서 새롭게 받은 data로 studyState를 업데이트 해준다.

onChange={(event, editor) => {
	const data = editor.getData();
	setStudyState({
		...studyState,
		contents: data
    });
}}

파일이 업로드되면 추가된 file로 studyState 업데이트 해준다.

loader.file.then((file) => {
	console.log(studyState); // {contents: '', editor: '', files: Array(0)} 엥?? 값들이 사라졌어요..
	setStudyState({
		...studyState,
		files: [...studyState.files, file],
	});
});
    

그런데 글을 작성하고 사진을 업로드하고 console.log(studyState); 를 찍었을 때 기존에 작성했던 글이 사라지고 {contents: '', editor: '', files: Array(0)} 이렇게 찍히더라.

처음에는 setStudyState를 내가 잘 못했는 줄 알고 몇시간동안은 계속 로그를 찍고 찾아봤다.
사실 처음에는 뭐가 문제인지도 모르겠고, 어떻게 해결해야할지도 모르겠었다.
근데 도저히 이해가 가지않았다. 왜 state가 초기값으로 찍히는지 🧐
그러던 도중에 힌트를 얻게되었다.

문제의 원인

코드를 유심히 보니 upload 가 클로저인데, 클로저에 관한 문제인가? 라는 생각을 먼저 하게되었다.
그래서 구글에 react state closure 라고 검색을 했다.
했더니 나와 유사한 issue를 겪고 있는 stack overflow의 글을 발견했다.

stack overflow에 나온 코드

import React, { useState} from "react";
import ReactDOM from "react-dom";

function App() {
  const [count, setCount] = useState(0);

  function handleAlertClick(){
    return (setTimeout(() => { alert("You clicked on: " + count);},3000))
  }


  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
      <button onClick={handleAlertClick}>Show alert</button>
    </div>
  );
}

이 이슈는 show alert를 누르면 count를 3초뒤에 보여주는데,
show alert를 누르고 그사이에 Click me 버튼을 누르면 다시 count가 증가하는데 alert창에 뜨는것은 증가하기전에 count를 보여주는것이었다.

보니까 내코드와 상당히 유사했다.

답변 : The reason the alert shows the outdated value of count is because the callback passed to setTimeout is referencing an outdated value of count captured by the closure. This is usually referred to as a stale-closure.

그 이유는 closure에 의해 캡쳐된 setTimeout이 오래된 값을 참조하기 때문이라고 했다.
이를 stale-closure 이라고 일반적으로 부른다고 적혀있었다.

해결방안

사실 이제 이유는 아까 설명대로 closure가 오래된 값을 참조하기 때문이라고 알았기 때문에 거의 다 해결되었다.
어떻게 하면 오래된 값말고 최신의 state를 가져올 수 있을까 공식문서를 뒤지다가 발견했다.
해결방안은 setState에 객체 대신 함수를 전달하는것이다.

이렇게 변경해주니 해결되었다.

setStudyState((studyState) => {
	return {
		...studyState,
		files: studyState.files.push(file),
    };
});

한가지의 해결방안을 더 찾았는데 공식문서에서는 ref를 이용하라고 나와있다.
이것은 각자 알아서 참고하시길!

📌 회고

간단한 해결방안이지만 2일동안 삽질을 했는데 나는 오히려 좋았다..! 이 이슈를 겪지 않았다면 나는 stale-closure, 왜 setState에 객체 대신 함수를 써야하는지 몰랐을 것이다.
그리고 정말 해결하지 못할것 같은 문제였는데도 끈기있게 계속 찾아보다 보니 내가 뭐든 해결할 수 있을것 같은 자신감을 주었다.
사실 나는 해결방안을 찾기 보다는 이거는 왜 그런거지? 이런식으로 궁금해서 해본거라 해결방안이 쉽던 어렵던 상관이없다!! 궁금한것을 해결하면서 오히려 더 많은 것을(stale-closure,setState,문제 해결 프로세스, 자신감 등) 많은것을 얻게된 경험이었던 것 같다.

참고자료

https://stackoverflow.com/questions/62806541/how-to-solve-the-react-hook-closure-issue
https://ko.reactjs.org/docs/faq-state.html
https://hewonjeong.github.io/deep-dive-how-do-react-hooks-really-work-ko/
https://dmitripavlutin.com/react-hooks-stale-closures/

profile
강지영입니...🐿️

0개의 댓글