안녕하세요 ~ 방금 가입하고 첫 질문 드립니다. Apollo Client에서 하나의 mut...
# 질문
u
안녕하세요 ~ 방금 가입하고 첫 질문 드립니다. Apollo Client에서 하나의 mutation을 Array.forEach와 같은 반복문으로 여러 번 실행시켰을 때, cache update가 정상적으로 되지 않네요. 이러한 상황에 대한 해결책이 있을까요 ? 예시 코드
Copy code
const onClickSave = () => {
  data.examples.forEach({a, b} => mutation({
    variables: {
      input: {
        a,
        b,
      }
    }
  }))
}
위와 같이 mutation을 실행했을 때 race condition에 빠져 cache update가 infinite loop에 빠지는 것 같습니다. 이렇게 추정한 이유는 아래 코드처럼 cache update할 시간을 mutation당 0.1초씩 줬더니 race condition에 빠지지 않고 정상적으로 cache update가 되었기 때문입니다.
Copy code
const onClickSave = () => {
      data.examples.forEach(({a, b}, idx) => setTimeout(() => mutation({
        variables: {
          input: {
            a,
            b,
          }
        }
      }), idx * 100))
    }
많은 답변 부탁드립니다. (_ _)
👍 2
t
질문 감사드려요 ㅎㅎ cache 업데이트가 아얘 안되는건가요??
❤️ 1
u
cache update는 되는데 무한적으로 반복됩니다... 😞
t
혹시 JS 환경이라면, Promise.all로 해도 마찬가지일까요??
u
넵 Promise.all로 묶어도 똑같더라구용...
t
흠… 개인적인 생각으로는 하나의 쿼리로 묶는게 맞지않나 하는 생각이긴해요
u
mutation 내 update 메소드에 console을 찍어보면 계속 반복적으로 찍히면서 렌더가 너무 많이 된다고 react에서 오류를 뿜내용
ohh 1
t
그치만 뮤테이션이 그렇게 나오지 않아서 그런거겠죠 ㅋㅋ
u
넵넵 그래서 지금 서버분이랑 list를 args로 넘기는게 맞나에 대한 고찰을 하는 중이었습니당.
보통 어떻게 하시나용 ?
t
글쎄요 저라면 저런 경우면 Mutation 필드를 하나 더 뺄거같긴해요. for를 돌면서 쿼리하는게 엄청 이상하긴 하니까요. 앞에 batch 네임스페이스 붙여서 하나 더 만들꺼같아요 ㅋㅋ
h
배치를 지원하지 않는 뮤테이션을 배치로 쓸 때인거죠? 보통은 클라이언트에서 커스텀 배치 뮤테이션을 만들어 사용합니다
ohh 2
t
오 혜성님이 말씀하신 방법이 짱이네요
u
오.. 커스텀 배치 뮤테이션으로 검색해보겠습니다...
t
그 apollo client 생성하실때
h
검색해도 안나와요 ㅋㅋㅋ
안타깝게도
t
resolvers 필드에 resolver 만드셔서 넣으면 돼요
u
오... 우선 해보고 모르겠으면 다시 질문 드리겠습니다 ! 👍
t
@Hyeseong Kim 근데 그러면 custom resolver 안에서 axios 같은 http client로 콜을 하나요?
h
아뇨 그냥 그대로 apollo client 써요
t
뭔가 for로 여러면 http 콜하는게 graphql 스럽진 않은거같아서요
h
context에 넘어옵니다
t
그렇다고 쿼리 스트링을 interpolation하는것도 뭔가 이상하고..
h
그 사실 apollo-link-dedup 이나 apollo-link-data-loader 같은 녀석들이 똑같은 짓을 해요
👍 1
debounce stack 에 묶어놨다가 한 쿼리로 묶어서 주고받는 식으로
t
ㅋㅋㅋㅋㅋ 그렇군요 ㅋㅋㅋ
서버쪽에서 로깅하기 힘들거같은데;;
h
비슷하게 ad-hoc 하게 구현할 수 있습니다
apollo-server 쓰시면 별 문제 없어요
그리고 원래 올려주신 이슈는
mutation 때문이라기보단 캐시 의존하고 있는 쿼리 다른게 문제일 거에요
mutation 하나 fullfiled 될 때마다 컴포넌트가 리렌더 되면서
그걸 다시 args 로 넘기고 있거나 하실듯
u
넵넵 맞아용 ~
u
해당 라이브러리 사용해서 해결해보도록 할게용 ~
h
mutation call 들을 Promise.all 로 묶어 한 번에 처리하는 배치 같은걸 만드실 수 있을텐데 이게 어차피 서버에서 지원 안하면 트랜잭셔널한 처리는 못해요
아 이건 구현 참고하라고 올려드린거고
음…
컴포넌트를 캐시에서 인디렉션 하시는게 맞는거같은데
u
그렇게 하면 mutation 후 서버에서 온 데이터를 다시 유저에게 보여주지 않아서 잘못된 데이터가 와도 유저에게 못 보여주지 않나용 ?
h
해당 mutation 이 캐시에 직접 의존하지 않고 사용자 액션(clickSave)에만 의존하도록 잘 인디렉션 해보시는게 해답일 듯 해요
mutation body는 캐시를 통해 전파되니까 꼭 직접 받지 않으셔도 되고요
이 변경을 mutation 이 스스로 재 참조 해서 문제가 발생하는거니까 (실제로 요청도 무한히 한다는 소리)
이 부분만 잘 제어해보세요. 멀리보면 서버에 배치 오퍼레이션 추가를 요청하는것도 좋습니다.
아님 apollo 같은 경우 쿼리 차단하고 explicit 하게 보낼 수 있어요
useQuery -> useLazyQuery
정 어려우면 이걸로 때울 수도 있으실듯
u
제가 지금 A라는 컴포넌트를 한 화면에서 여러개 보여주고 해당 컴포넌트들의 변화를 onSave때 모두 한번에 mutation해야해서, cache에 저장 후, onSave때 cache에 저장돼있는 값을 불러와서 mutation하는데 이 과정이 잘못된 건가요 ?
현재 mutation은 유저의 onSave action에 dep가 걸려있긴 합니당 !
h
제가 지금 A라는 컴포넌트를 한 화면에서 여러개 보여주고 해당 컴포넌트들의 변화를 onSave때 모두 한번에 mutation해야해서, cache에 저장 후, onSave때 cache에 저장돼있는 값을 불러와서 mutation하는데 이 과정이 잘못된 건가요 ?
mutation 이 제각각 호출된다고 했을 때, 이게 별 순서 없이 제각각 업데이트 되는건 사용자 인지적으로 썩 좋은 경험은 아니라서 별도의 트랜지션을 정의하셔야 해요. 그리고 mutation을 기다리는 동안은 사용자 액션도 막아야 할 거구요. 저라면 마찬가지로 일단 변경 사항을 더 상위 컨텍스트에서 모아두겠지만, 뮤테이션은 그 컨텍스트를 제어하는 곳(부모 컴포넌트나 글로벌 스토어, 커스텀 뮤테이션도 해당됨)에서 다루고 트랜지션이 일어나는 동안은 별도의 로딩 UX를 제공하려 할 것 같네요.
❤️ 1
u
@Hyeseong Kim 정성스러운 답변 정말 감사합니당 ! Hyeseong님의 말씀이 UX 관점에서 좋네용 ! 감사합니당 ~
e
응답 받는 타입에서 ID scalar 사용하시나요? ID 스칼라를 사용하고, 생성된 아이템에 동일한 값을 반환하시면 apollo clinet cache 오동작 할 수 있습니다