🍔 핵심 내용
🥑 comment 작성 기능을 만들어 보자.
로그인, 회원가입 시 일부 활용 했던 useForm기능을 다시 사용할 것이다.
🍔 코드 리뷰
🥑 Comments.js
import PropTypes from "prop-types";
import { useForm } from "react-hook-form";
import styled from "styled-components";
import Comment from "./Comment";
import { gql, useMutation } from "@apollo/client";
const CREATE_COMMENT_MUTATION = gql`
mutation createComment($photoId: Int!, $payload: String!) {
createComment(photoId: $photoId, payload: $payload) {
ok
error
}
}
`;
const CommentsContainer = styled.div`
margin-top: 20px;
`;
const CommentCount = styled.span`
opacity: 0.7;
margin: 10px 0px;
display: block;
font-weight: 600;
font-size: 10px;
`;
function Comments({ photoId, author, caption, commentNumber, comments }) {
const [createCommentMutation, { loading }] = useMutation(
CREATE_COMMENT_MUTATION
);
const { register, handleSubmit, setValue } = useForm();
const onValid = (data) => {
const { payload } = data;
if (loading) {
return;
}
createCommentMutation({
variables: {
photoId,
payload,
},
});
setValue("payload", "");
};
return (
<CommentsContainer>
<Comment author={author} payload={caption} />
<CommentCount>
{commentNumber === 1 ? "1 comment" : `${commentNumber} comments`}
</CommentCount>
{comments?.map((comment) => (
<Comment
key={comment.id}
author={comment.user.userName}
payload={comment.payload}
/>
))}
<div>
<form onSubmit={handleSubmit(onValid)}>
<input
name="payload"
ref={register({ required: true })}
type="text"
placeholder="Write a comment..."
/>
</form>
</div>
</CommentsContainer>
);
}
Comments.propTypes = {
photoId: PropTypes.number.isRequired,
author: PropTypes.string.isRequired,
caption: PropTypes.string,
commentNumber: PropTypes.number.isRequired,
comments: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.number.isRequired,
user: PropTypes.shape({
avatar: PropTypes.string,
userName: PropTypes.string.isRequired,
}),
payload: PropTypes.string.isRequired,
isMine: PropTypes.bool.isRequired,
createdAt: PropTypes.string.isRequired,
})
),
};
export default Comments;
프론트 단의 form 데이터를 백으로 보내는 로직은 크게 보면 아래와 같다.
1) gql 만들고 useMutation 사용
- 먼저 gql 구문을 만들고 ( const CREATE_COMMENT_MUTATION ) useMutation으로 호출한다. 그리고 useMutation의 첫번째 배열 값에는 해당 mutation을 실행하는 함수 값이 들어간다.
2) useForm 사용
- useForm에는 여러 키 값이 들어간다. register로는 유효성 검사, handleSubmit으로는 form 제출시 발동할 함수를, setValue는 value값 세팅을 할 수 있다. (이를 활용하여 setValue는 form 제출 후, 해당 form을 빈 값으로 세팅)
3) 실제 form에서의 사용
- ref를 통해 register를 등록하고, handleSubmit의 인자값으로 별도 만든 함수를 넣는다(onValid함수). 이때 onValid에는 form에서 제출한 data 값이 들어간다 (이때 data값은 해당 form의 name 값이다). 그리고 onValid함수 마지막에는 백으로 데이터를 넘겨주는 createCommentMutation함수가 발동되고 해당 인자값으로 form에서 받은 data(payload값)와 상위 컴포넌트에서 받은 photoId 값이 들어가게 되어, 백으로 해당 인자가 넘어 간다.
그리고 끝으로, form 제출 후에는 다시 빈 값으로 세팅하기 위해 setValue가 발동 된다.
여기 까지 하게 되면, createCommentMutation이 문제 없이 작동 되어 새로고침 하게 되면 comment가 발동 되어 있다. 문제는 엔터 후 바로 보여지는게 아니라 새로고침을 해야지 보여진다. 이 문제를 해결 해보자.
🍔 핵심 내용
🥑 comment 작성 기능을 만들어 보자. 실시간 반영 부분. (코멘트 실시간 반영 + 코멘트 수 실시간 증가)
이전에 좋아요 누르면 바로 cache를 활용하여 화면에 반영되는 것을 배웠다. 이것도 비슷한 개념이다.
🍔 코드 리뷰
🥑 Comments.js
import PropTypes from "prop-types";
import { useForm } from "react-hook-form";
import styled from "styled-components";
import Comment from "./Comment";
import { gql, useMutation } from "@apollo/client";
import useUser from "../../hooks/useUser";
const CREATE_COMMENT_MUTATION = gql`
mutation createComment($photoId: Int!, $payload: String!) {
createComment(photoId: $photoId, payload: $payload) {
ok
error
id
}
}
`;
const CommentsContainer = styled.div`
margin-top: 20px;
`;
const CommentCount = styled.span`
opacity: 0.7;
margin: 10px 0px;
display: block;
font-weight: 600;
font-size: 10px;
`;
const PostCommentContainer = styled.div`
margin-top: 10px;
padding-top: 15px;
padding-bottom: 10px;
border-top: 1px solid ${(props) => props.theme.borderColor};
`;
const PostCommentInput = styled.input`
width: 100%;
&::placeholder {
font-size: 12px;
}
`;
function Comments({ photoId, author, caption, commentNumber, comments }) {
const { data: userData } = useUser();
const { register, handleSubmit, setValue, getValues } = useForm();
const createCommentUpdate = (cache, result) => {
const { payload } = getValues();
setValue("payload", "");
const {
data: {
createComment: { ok, id },
},
} = result;
if (ok && userData?.me) {
const newComment = {
__typename: "Comment",
createdAt: Date.now() + "",
id,
isMine: true,
payload,
user: {
...userData.me,
},
};
cache.modify({
id: `Photo:${photoId}`,
fields: {
comments(prev) {
return [...prev, newComment];
},
commentNumber(prev) {
return prev + 1;
},
},
});
}
};
const [createCommentMutation, { loading }] = useMutation(
CREATE_COMMENT_MUTATION,
{
update: createCommentUpdate,
}
);
const onValid = (data) => {
const { payload } = data;
if (loading) {
return;
}
createCommentMutation({
variables: {
photoId,
payload,
},
});
};
return (
<CommentsContainer>
<Comment author={author} payload={caption} />
<CommentCount>
{commentNumber === 1 ? "1 comment" : `${commentNumber} comments`}
</CommentCount>
{comments?.map((comment) => (
<Comment
key={comment.id}
author={comment.user.userName}
payload={comment.payload}
/>
))}
<PostCommentContainer>
<form onSubmit={handleSubmit(onValid)}>
<PostCommentInput
name="payload"
ref={register({ required: true })}
type="text"
placeholder="Write a comment..."
/>
</form>
</PostCommentContainer>
</CommentsContainer>
);
}
Comments.propTypes = {
photoId: PropTypes.number.isRequired,
author: PropTypes.string.isRequired,
caption: PropTypes.string,
commentNumber: PropTypes.number.isRequired,
comments: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.number.isRequired,
user: PropTypes.shape({
avatar: PropTypes.string,
userName: PropTypes.string.isRequired,
}),
payload: PropTypes.string.isRequired,
isMine: PropTypes.bool.isRequired,
createdAt: PropTypes.string.isRequired,
})
),
};
export default Comments;
기본적인 개념은 아래 cache에 fake식으로 먼저 화면에 보여줄 수 있도록 modify 해주는 개념이다. (이전에 좋아요 기능과 같이) 이때 활용된 것들은
1) useUser 훅 사용 --> cache의 user 부분을 만들기 위해서
2) useForm의 getValues 부분 추가 --> cache의 payload를 채우기 위해서 (form의 value값을 cache의 payload에 넣어주는식)
3) fileds의 comments부분에서 return 값에는 배열 값이 들어 가는데 기존 comment 값들이 들어가 있기 때문에,,,
따라서 [ ...prev, newComment ] 식으로 리턴값이 나와야 함. 이 뜻은, 기존 배열 값 유지 + 새로운 comment 를 해주겠다는 것이다.
나머지 부분은 이해 할 수 있는 수준이다.
🍔 핵심 내용
🥑 comment 작성 기능을 만들어 보자. 실시간 반영 부분. (코멘트 실시간 반영 + 코멘트 수 실시간 증가) 두번째
이 다음 내용인, 코멘트 삭제기능을 사용하기 위해서는 Photo안에 string? fake?식으로 해놓는걸로는 안된다. 왼쪽에 정식으로 cache인 Comment가 생기고, Photo안에서는 ref로 되어야 한다.
따라서, 배열 값에는 기존 값 + 새롭게 추가된 cache 값이 들어와야 한다. 이때 이전에 했던 cache.writeFragment를 사용하면 된다. 이 기능이 정식으로 Fragment를 만들어 준다.
*** cache는 제2의 데이터베이스라고 보면 된다.
🍔 코드 리뷰
🥑 Comments.js
...
if (ok && userData?.me) {
const newComment = {
__typename: "Comment",
createdAt: Date.now() + "",
id,
isMine: true,
payload,
user: {
...userData.me,
},
};
const newCacheComment = cache.writeFragment({
data: newComment,
fragment: gql`
fragment BSName on Comment {
id
createdAt
isMine
payload
user {
userName
avatar
}
}
`,
});
cache.modify({
id: `Photo:${photoId}`,
fields: {
comments(prev) {
return [...prev, newCacheComment];
},
commentNumber(prev) {
return prev + 1;
},
},
});
}
};
...
🍔 핵심 내용
🥑 comment 삭제 기능을 만들어 보자. (실시간 삭제 + 코멘트 수 실시간 낮추기)
cache에 comment가 생겼기 때문에(바로 이전 내용) 이제 실시간으로 cache삭제도 가능하다.
비슷하게, 코멘트 수도 실시간으로 낮출 수 있다.
🥑Comment.js
import { gql, useMutation } from "@apollo/client";
import React from "react";
import PropTypes from "prop-types";
import styled from "styled-components";
import { FatText } from "../shared";
import { Link } from "react-router-dom";
const DELETE_COMMENT_MUTATION = gql`
mutation deleteComment($id: Int!) {
deleteComment(id: $id) {
ok
}
}
`;
const CommentContainer = styled.div`
margin-bottom: 7px;
`;
const CommentCaption = styled.span`
margin-left: 10px;
a {
background-color: inherit;
color: ${(props) => props.theme.accent};
cursor: pointer;
&:hover {
text-decoration: underline;
}
}
`;
function Comment({ id, photoId, isMine, author, payload }) {
const updateDeleteComment = (cache, result) => {
const {
data: {
deleteComment: { ok },
},
} = result;
if (ok) {
cache.evict({ id: `Comment:${id}` });
cache.modify({
id: `Photo:${photoId}`,
fields: {
commentNumber(prev) {
return prev - 1;
},
},
});
}
};
const [deleteCommentMutation] = useMutation(DELETE_COMMENT_MUTATION, {
variables: {
id,
},
update: updateDeleteComment,
});
const onDeleteClick = () => {
deleteCommentMutation();
};
return (
<CommentContainer>
<FatText>{author}</FatText>
<CommentCaption>
{payload.split(" ").map((word, index) =>
/#[\w]+/.test(word) ? (
<React.Fragment key={index}>
<Link to={`/hashtags/${word}`}>{word}</Link>{" "}
</React.Fragment>
) : (
<React.Fragment key={index}>{word} </React.Fragment>
)
)}
</CommentCaption>
{isMine ? <button onClick={onDeleteClick}>❌</button> : null}
</CommentContainer>
);
}
Comment.propTypes = {
isMine: PropTypes.bool,
id: PropTypes.number,
photoId: PropTypes.number,
author: PropTypes.string.isRequired,
payload: PropTypes.string.isRequired,
};
export default Comment;
코멘트가 isMine이 true면 X 버튼이 활성화 된다. 그리고 해당 버튼을 누르면 onDeleteClick -> deleteCommentMutation함수가 발동 되고, 해당 mutation이 실행된다. 동시에 cache 부분도 update된다.
🥑Comments.js
...
{comments?.map((comment) => (
<Comment
key={comment.id}
id={comment.id}
photoId={photoId}
author={comment.user.username}
payload={comment.payload}
isMine={comment.isMine}
/>
))}
...
id, photoId, isMine props 값을 전달해 주었다.
'코딩강의 > 인스타그램클론(expo-노마드코더)' 카테고리의 다른 글
REACT NATIVE SETUP (0) | 2021.06.28 |
---|---|
Profile (0) | 2021.06.20 |
FEED (2) (0) | 2021.06.09 |
FEED (1) (0) | 2021.05.26 |
LOGIN AND SIGNUP (2) (0) | 2021.05.05 |