본문 바로가기
GraphQL

Fullstack Part 8 #4 Login & Cache update

by kicksky 2021. 3. 11.

fullstackopen.com/en/part8/login_and_updating_the_cache 읽고 정리한 글.

 

Fullstack part8 |

Open online course on Javascript based modern web development by University of Helsinki and Houston Inc..

fullstackopen.com

목차

🔸 유저 로그인

 - 로그아웃 시, apollo client의 캐시를 지우는 resetStore

🔸 헤더에 토큰 추가

 - httpLink, apollo-link-context로 아폴로가 서버와 연결되는 방식에 접근하여 헤더를 수정

🔸 캐시 업데이트, revisited

 - useMutation의 update 옵션을 통해 최적화. readQuery, writeQuery

 

유저 로그인

- useState로 token state를 생성 및 관리하고, useMutation으로 login mutation 생성한다.

- 로그인: login mutation의 결과가 들어오면 유저 토큰을 각각 token state와 local storage에 저장한다.

- 로그아웃: token state를 null로 업데이트 하고, local storage 값 삭제할 뿐만 아니라 Apllo client의 캐시도 리셋한다.

const App = () => {
  const [token, setToken] = useState(null)

  // ...

  if (!token) {
    return (
      <div>
        <Notify errorMessage={errorMessage} />
        <h2>Login</h2>
        <LoginForm
          setToken={setToken}
          setError={notify}
        />
      </div>
    )
  }

  return (
    // ...
  )
}

//...

export const LOGIN = gql`
  mutation login($username: String!, $password: String!) {
    login(username: $username, password: $password)  {
      value
    }
  }
`
  const [ login, result ] = useMutation(LOGIN, {
    onError: (error) => {
      setError(error.graphQLErrors[0].message)
    }
  })

  useEffect(() => {
    if ( result.data ) {
      const token = result.data.login.value
      setToken(token)
      localStorage.setItem('phonenumbers-user-token', token)
    }
  }, [result.data]) // eslint-disable-line

resetStore

- Apollo client 객체의 메소드이며, 앞서 말한 Apollo client의 캐시를 리셋한다.

- 여기에서는 useApolloClient Hook으로 client 객체를 가져왔다.

const App = () => {
  const [token, setToken] = useState(null)
  const [errorMessage, setErrorMessage] = useState(null)
  const result = useQuery(ALL_PERSONS)
  const client = useApolloClient()

  if (result.loading)  {
    return <div>loading...</div>
  }

  const logout = () => {
    setToken(null)
    localStorage.clear()
    client.resetStore() // <-
  }

}

 

헤더에 토큰 추가

- 서버에 요청할 때, 헤더에 토큰을 담아보내야 한다.

이를 위해 ApolloClient 객체의 link 파라미터를 수정한다. link는 아폴로가 서버에 연결되는 방식을 정의한다.

- apollo-link-context 라이브러리를 설치한다.

- httpLink 연결을 수정하여, 서버로 요청할 때 authorization 헤더가 local storage에 저장된 토큰을 담아 보내도록 한다.

(www.apollographql.com/docs/react/api/link/introduction/)

import { setContext } from 'apollo-link-context'

const authLink = setContext((_, { headers }) => {
  const token = localStorage.getItem('phonenumbers-user-token')
  return {
    headers: {
      ...headers,
      authorization: token ? `bearer ${token}` : null,
    }
  }
})

const httpLink = new HttpLink({ uri: 'http://localhost:4000' })

const client = new ApolloClient({
  cache: new InMemoryCache(),
  link: authLink.concat(httpLink)
})

+ )프론트에서 새 전화번호 등록할 때, phone 값을 입력하지 않으면 유효성 검사를 통과하지 못하고 에러 발생

phone 값을 삼항 연산자를 이용해 제어한다.

 

캐시 업데이트, revisited

새 유저를 전화번호부에 추가할 때, Apollo client 캐시를 업데이트 해야 한다. useMutation의 두 번째 파라미터로 refetchQueries 옵션을 주면 업데이트할 쿼리를 선택할 수 있다.

- 단점은 업데이트가 있을 때마다 rerun 한다는 점이다.

const PersonForm = ({ setError }) => {
  // ...

  const [ createPerson ] = useMutation(CREATE_PERSON, {
    refetchQueries: [  {query: ALL_PERSONS} ],
    onError: (error) => {
      setError(error.graphQLErrors[0].message)
    }
  })

최적화

캐시 업데이트를 직접 핸들링 하면 된다. useMutation의 두 번째 파라미터인 옵션에서 update 필드에 아폴로가 mutation 이후에 호출할 콜백 함수를 정의해주면 된다. 캐시와 mutation에 의해 리턴되는 데이터가 파라미터로 들어오면서 이 콜백 함수를 참조한다.

const PersonForm = ({ setError }) => {
  // ...

  const [ createPerson ] = useMutation(CREATE_PERSON, {
    onError: (error) => {
      setError(error.graphQLErrors[0].message)
    },
    update: (store, response) => {
      const dataInStore = store.readQuery({ query: ALL_PERSONS })
      store.writeQuery({
        query: ALL_PERSONS,
        data: {
          ...dataInStore,
          allPersons: [ ...dataInStore.allPersons, response.data.addPerson ]
        }
      })
    }
  })
 
  // ..
}  

- readQuery 함수로 ALL_PERSONS 쿼리의 캐시된 state를 읽고, writeQuery 함수로 캐시를 업데이트 한다. 새로 추가된 전화번호가 캐시된 데이터에 추가된다.

- 캐시가 특정된 쿼리를 충족시키는 데이터를 전부 담고 있지 않으면 readQuery가 에러를 던지는 것에 주의해야한다. try-catch 블록으로 잡도록 한다.

- 어떤 경우에는 update 콜백을 써야만 캐시를 항상 최신으로 유지할 수도 있다.

- 필요한 경우에는 전체 어플리케이션의 캐시나 단일 쿼리를 막아둘 수도 있다. fetchPolicy를 no-cache로 설정하면 캐시 사용을 제어할 수 있다. (www.apollographql.com/docs/react/data/queries/#configuring-fetch-logic)

 

캐시에 오래된 데이터가 있으면 버그를 찾기 어려울 수도 있으므로 부지런히 관리하도록 한다. 

There are only two hard things in Computer Science: cache invalidation and naming things. 

 

댓글