Room.js(2)

김종민·2022년 6월 13일
0

insta-native

목록 보기
32/36

들어가기
대화방에서 subscription되는 방법을 알아본다.

1.screens/Room.js

import { gql, useApolloClient, useMutation, useQuery } from '@apollo/client'
import React, { useEffect, useState } from 'react'
import { useForm } from 'react-hook-form'
import { View, Text, KeyboardAvoidingView, FlatList } from 'react-native'
import styled from 'styled-components/native'
import ScreenLayout from '../components/ScreenLayout'
import useMe from '../hooks/useMe'
import { Ionicons } from '@expo/vector-icons'

const ROOM_UPDATES = gql`
  subscription roomUpdates($id: Int!) {
    roomUpdates(id: $id) {
      id
      payload
      user {
        username
        avatar
      }
      read
    }
  }
`
///1) server부분에서 만든 roomUpadte subscription을 불러들인다.
///variables는 room number이고, 받는 data는 message 모양이다.


const SEND_MESSAGE_MUTATION = gql`
  mutation sendMessage($payload: String!, $roomId: Int, $userId: Int) {
    sendMessage(payload: $payload, roomId: $roomId, userId: $userId) {
      ok
      error
      id
    }
  }
`

const ROOM_QUERY = gql`
  query seeRoom($id: Int!) {
    seeRoom(id: $id) {
      id
      messages {
        id
        payload
        user {
          username
          avatar
        }
        read
      }
    }
  }
`
const MessageContainer = styled.View`
  padding: 0px 10px;
  flex-direction: ${(props) => (props.outGoing ? 'row-reverse' : 'row')};
  align-items: flex-end;
`
const Author = styled.View``
const Avatar = styled.Image`
  width: 30px;
  height: 30px;
  border-radius: 25px;
  background-color: rgba(255, 255, 255, 0.2);
`
const Username = styled.Text`
  color: yellowgreen;
`
const Messaage = styled.Text`
  color: white;
  background-color: ${(props) => (props.outGoing ? 'yellowgreen' : 'skyblue')};
  border: 1px solid rgba(255, 255, 255, 0.5);
  padding: 10px 20px;
  border-radius: 50px;
  margin: 5px 20px;
`
const Input = styled.TextInput`
  background-color: rgba(255, 255, 255, 0.1);
  padding: 10px 20px;
  border: 2px solid white;
  border-radius: 100px;
  color: white;
  width: 90%;
  margin-right: 10px;
`

const InputContainer = styled.View`
  width: 95%;
  margin-bottom: 50px;
  margin-top: 25px;
  flex-direction: row;
  align-items: center;
`

const SendButton = styled.TouchableOpacity``

export default function Room({ route, navigation }) {
  const { data: meData } = useMe()

  const { register, setValue, handleSubmit, getValues, watch } = useForm()
  const updateSendMessage = (cache, result) => {
    const {
      data: {
        sendMessage: { ok, error, id },
      },
    } = result
    if (ok && meData) {
      const { message } = getValues()
      setValue('message', '')
      const messageObj = {
        id,
        payload: message,
        user: {
          username: meData.me.username,
          avatar: meData.me.avatar,
        },
        read: true,
        __typename: 'Message',
      }
      const messageFragment = cache.writeFragment({
        fragment: gql`
          fragment NewMessage on Message {
            id
            payload
            user {
              username
              avatar
            }
            read
          }
        `,
        data: messageObj,
      })
      cache.modify({
        id: `Room:${route.params.id}`,
        fields: {
          messages(prev) {
            return [...prev, messageFragment]
          },
        },
      })
    }
  }
  const [sendMessageMutation, { loading: sendingMessage }] = useMutation(
    SEND_MESSAGE_MUTATION,
    {
      refetchQueries: [
        { query: ROOM_QUERY, variables: { id: route.params.id } },
      ],

      // update: updateSendMessage,
    }
  )

  console.log(route)
  const { data, loading, refetch, subscribeToMore } = useQuery(ROOM_QUERY, {
  /////subscribeToMore 집중해서 볼것, 반드시 여기에 넣어주어야함.
  
    variables: {
      id: route?.params?.id,
    },
  })

  const client = useApolloClient()
  ///2)cache 사용을 위해서 useApolloClient를 이용한다.
  
  const updateQuery = (prevQuery, options) => {
  ///4)updateQuery 함수만드는 방법
  
    const {
      subscriptionData: {
        data: { roomUpdates: message },
      },
    } = options
    ///5)updateQuery는 위에서 보듯이 prevQuery와 options를 받는데,
    ///options에서 subscriptionData를 받을 수 있음.
    ///console.log(options)를 하면 options의 모양을 확인 가능함.
    
    if (message.id) {
      const incomingMessage = client.cache.writeFragment({
        fragment: gql`
          fragment NewMessage on Message {
            id
            payload
            user {
              username
              avatar
            }
            read
          }
        `,
        data: message,
      })
      ///6)5번에서 받은 message.id의 존재를 확인하면, incomingMessage를
      ///cache에 write한다. 모양은 roomUpadte subscription과 같은 모양.
      ///client가 들어간 이유는 여기서는 cache를 못받아서
      ///useApolloClient를 사용한 모양이다.
      
      client.cache.modify({
        id: `Room:${route.params.id}`,
        fields: {
          messages(prev) {
            const existingMessage = prev.find(
              (aMessage) => aMessage.__ref === incomingMessage.__ref
            )
            console.log(prev)
            console.log('----------------------')
            console.log(incomingMessage)
            if (existingMessage) {
              return prev
            }
            return [...prev, incomingMessage]
          },
          ///7)위에서 만든 incomingMessage로 cache를 modify해준다.
          ///같은 메세지가 중복으로 뿌려지기 떄문에,
          ///existingMessage를 만들어서, 존재했전 메세지는
          /// return prev만 시킨다.
          ///그리고 그 외의 부분은 return [...prev, incomingMessage]를
          ///return한다.
          ///위의 __ref모양은 console.log(incomingMessage)를 찍어보면
          ///모양이 저렇게 나옴.
          
        },
      })
    }
  }
  
  const [subscribed, setSubscribed] = useState(false)
  useEffect(() => {
    if (data?.seeRoom && !subscribed) {
      subscribeToMore({
        document: ROOM_UPDATES,
        variables: {
          id: route?.params?.id,
        },
        updateQuery,
      })
      setSubscribed(true)
    }
  }, [data, subscribed])
  ///3)useState 설정은 해야되는지, 확실히 아직 잘 모르겠음.
  ///여기서 가장 중요한 것은 subscribeToMore임.
  ///그 아래, document는 위애서 만든 subscription이며, 
  ///variables는 room의 id.
  ///그리고 updateQuery는 여기에 다 작성하면 복잡하니, 위에 따로 함수로 만들어줌,
  ///subscribeToMore는 ROOM_QUERY(seeRoom)에다가 반드시 넣어주어야 함.

  const onValid = ({ message }) => {
    if (!sendingMessage) {
      sendMessageMutation({
        variables: {
          payload: message,
          roomId: route?.params?.id,
        },
      })
      setValue('message', '')
    }
  }
  useEffect(() => {
    register('message', { required: true })
  }, [register])
  useEffect(() => {
    navigation.setOptions({
      title: `Talking with ${route?.params?.talkingTo?.username}`,
    })
  })
  const renderItem = ({ item: message }) => (
    <MessageContainer outGoing={message.user.username === meData?.me?.username}>
      <Author>
        <Avatar source={{ uri: message.user.avatar }} />
        <Username>{message.user.username}</Username>
      </Author>
      <Messaage outGoing={message.user.username === meData?.me?.username}>
        {message.payload}
      </Messaage>
    </MessageContainer>
  )
  const refresh = async () => {
    setRefreshing(true)
    await refetch()
    setRefreshing(false)
  }
  const [refreshing, setRefreshing] = useState(false)
  const messages = [...(data?.seeRoom?.messages ?? [])]
  messages.reverse()
  return (
    <KeyboardAvoidingView
      style={{ flex: 1, backgroundColor: 'black' }}
      behavior="height"
      keyboardVerticalOffset={60}
    >
      <ScreenLayout loadng={loading}>
        <FlatList
          ItemSeparatorComponent={() => <View style={{ height: 5 }}></View>}
          refreshing={refreshing}
          onRefresh={refresh}
          inverted
          style={{ width: '100%', marginVertical: 30 }}
          data={messages}
          keyExtractor={(message) => '' + message.id}
          renderItem={renderItem}
        />
        <InputContainer>
          <Input
            placeholder="Write a message..."
            placeholderTextColor="white"
            returnKeyLabel="Send Message"
            returnKeyType="send"
            onChangeText={(text) => setValue('message', text)}
            onSubmitEditing={handleSubmit(onValid)}
            value={watch('message')}
          />
          <SendButton
            onPress={handleSubmit(onValid)}
            disabled={!Boolean(watch('message'))}
          >
            <Ionicons
              name="send"
              color={
                !Boolean(watch('message')) ? 'rgba(255,255,255,0.5)' : 'white'
              }
              size={30}
            />
          </SendButton>
        </InputContainer>
      </ScreenLayout>
    </KeyboardAvoidingView>
  )
}
profile
코딩하는초딩쌤

0개의 댓글