노션클론 리팩토링 (6) - BreadCrumb버그 fix, api호출대신 리덕스 함수 만들어 사용하기

김영현·2024년 2월 17일
0

서론

오랜만의 리팩토링이다. 어영부영하다가 미뤘는데, 오늘 생각나서 조금 건드려봄!
문서 생성후 breadCrumb가 바로 업데이트되지 않는 버그를 해결해보자.


원인은 single truth of source

single truth of source : 모든 데이터 요소를 한 군데에서만 조작해야함

나만의 리덕스를 만들고 useSelector, dispatch를 사용했으나, 각 컴포넌트의 setState로직과 혼재함.
=> 이때문에 문서 생성시 redux를 거치지 않았다.

// 문서 생성 로직
  async createDocument(id = null) {
    const body = { title: "제목 없음", parent: id };
    const response = await request("/documents", {
      method: "POST",
      body: JSON.stringify(body),
    });
    this.getDocuments();
    push(`/documents/${response.id}`);
    return response;
  }

//문서 생성 버튼 로직
onClick: async () => {
      const response = await this.createDocument();
      const storage = new Storage(window.localStorage);
      storage.setItem(response.id, { isFolded: true });
  },

그냥 단순하게 문서 생성후 push를 이용하여 라우팅만 한다. 여기에 dispatch를 도입해보자.

그러려면, 리덕스 모듈부터 바꿔주어야한다. Ducks패턴을 이용한 documentDucks에는 문서를 생성하는 액션 생성자 함수가 없다.


step 1. 생성자 함수 만들기

생성또한 api를 이용하기때문에 비동기로 작업해야한다.
따라서 thunk 생성자 함수를 만든다. 이때 createDocument에서 사용한 로직은 대부분 가져오면 됨!

export const createDocumentAsync =
  (parentId = null) =>
  async (dispatch) => {
    try {
      const body = { title: "제목 없음", parent: parentId };
      const createdDocument = await request("/documents", {
        method: "POST",
        body: JSON.stringify(body),
      });

      dispatch({
        type: CREATE_DOCUMENT,
        payload: createdDocument,
      });
    } catch (e) {
      console.error(e);
    }
  };

이렇게 만들고

페이지 추가버튼 누르니 생성은 된다.

되는데....


step 1에서 문제발생!) 문서목록 업데이트가 간헐적으로 안됨

//Nav컴포넌트 render메서드에서 호출
    const data = store.useSelector((state) => state.documentsReducer.documents);
    const selectedDocument = store.useSelector(
      (state) => state.documentsReducer.selectedDocument
    );

//Nav컴포넌트 render메서드 내부에서 자식으로 호출하는 Button 컴포넌트에 props로 전달한다.
       onClick: async () => {
          store.dispatch(createDocumentAsync());
          this.getDocuments();
          const storage = new Storage(window.localStorage);
          storage.setItem(selectedDocument.id, { isFolded: true });
        },
          
 //this.getDocuments 로직        
getDocuments() {
    store.dispatch(fetchDocumentsAsync());
  }

해결법 1) 문서 생성 thunk액션 호출

왜 그런지 모르겠다.
하지만 this는 보통 예기치 못한 문제를 불러일으킨다.
따라서 this.getDocuments대신store.dispatch(fetchDocumentsAsync())를 사용해봤다.

잘 해결됐다.

해결법 2) this를 정확히 바인딩하자

this.getDocuments.apply(this)Nav컴포넌트를 바인딩하자.

잘 해결됐다.

=> 프롭스로 전달할땐 this를 명시적으로 바인딩해주자. 알고 있던 내용인데..ㅠㅠ

이후 상황

해결됐었는데, 다시 간헐적으로 업데이트가 안된다. 이전보다는 잘 되는데...
열심히 디버깅해보자...!!!


step 2. 기왕 이렇게된거 문서 삭제까지 redux로 넘기자

제목 그대로다! 문서 삭제까지 redux(나만의...)로 넘겨보자

//원래 로직
  async removeDocument(id) {
    await request(`/documents/${id}`, {
      method: "DELETE",
    });
    this.getDocuments();
    push("/");
  }

//documentDucks에 추가된 thunk
export const removeDocumentASync = (documentId) => async (dispatch) => {
  try {
    if (!documentId) {
      return alert("삭제하려는 문서 아이디를 지정해주세요");
    }

    const deletedDocument = await request(`/documents/${documentId}`, {
      method: "DELETE",
    });

    if (deletedDocument) {
      dispatch(fetchDocumentsAsync());
    }
  } catch (e) {
    console.error(e);
  }
};

dispatch내부에서 비동기 dispatch를 또 불러도 될까라는 확신이 없었지만...
결국 dispatch를 받아와서 사용하기때문에 해도 된다고 본다!

만약 선택한 문서를 삭제했다면?

selectedDocument에 값이 남아있고 삭제한 문서라면 값을 날려주면 된다

    case REMOVE_DOCUMENT: {
      const selectedDocument = checkSelectedDocument(
        action.payload,
        state.selectedDocument?.id
      )
        ? {}
        : state.selectedDocument;
      return {
        ...state,
        selectedDocument,
      };
    }

reducer에서 하지않고 thunk함수쪽으로 넘겨주는 게 좋아보인다...!
일단 이렇게 해두자.


step 2.5 ) 문서생성 후 아예 문서리스트를 업데이트 해주자

위의 step 2처럼 문서 삭제이후 바로 dispatch를 이용하여 문서목록을 업데이트 해주었다.
이 로직을 문서 생성에도 적용해보자

=> 곰곰히 생각해보니, 문서 생성은 생성만 하고 삭제는 삭제만 해야한다.
명시적으로 호출해주는게 낫지 않을까 싶지만...일단 추가해봄!

export const createDocumentAsync =
  (parentId = null) =>
  async (dispatch) => {
    try {
      const body = { title: "제목 없음", parent: parentId };
      const createdDocument = await request("/documents", {
        method: "POST",
        body: JSON.stringify(body),
      });

      dispatch({
        type: CREATE_DOCUMENT,
        payload: createdDocument,
      });

      if (createdDocument) {
        dispatch(fetchDocumentsAsync());
      }
    } catch (e) {
      console.error(e);
    }
  };

잘 됩니다!


step 3) 문서 생성 후 생성된 문서로 이동하기

지금까지 사용하던 push 메서드를 사용하면 될 것 같다.

//Nav.render() 일부

...
  render() {
    const data = store.useSelector((state) => state.documentsReducer.documents);
    const selectedDocument = store.useSelector(
      (state) => state.documentsReducer.selectedDocument
    );

    if (selectedDocument) {
      push(`/documents/${selectedDocument.id}`);
    }
...
}

//문서를 생성할땐
        onClick: async () => {
          store.dispatch(createDocumentAsync());
          const storage = new Storage(window.localStorage);
          storage.setItem(selectedDocument.id, { isFolded: true });
        },

마치 useEffect처럼 렌더될때 선택된 문서로 이동시켜준다.

documents/undefined로 이동하는 버그 (해결)

Nav렌더링할때

    if (selectedDocument) {
      push(`/documents/${selectedDocument.id}`);
    }

이렇게 선택된 문서를 체크한다. 이때 문서가 객체여서 {}truty로 판단되어 로직이 실행됨.
아래처럼 배열화하여 길이를 체크하면된다

    if (Object.keys(selectedDocument).length) {
      push(`/documents/${selectedDocument.id}`);
    }

완성!

완성본은 여기서 볼수있다!

다음엔 컴포넌트를 분리하고 남아있는 버그도 수정해보겠다!(있는지 모르겠는데 있겠지...?)


profile
모르는 것을 모른다고 하기

0개의 댓글