들어가기
대화방에서 subscription되는 방법을 알아본다.
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>
)
}