글쓰는쿼카의 PM 여정
[팀프로젝트] 게시글 CRUD 구현 (2024. 6. 6.) 본문
#스파르타코딩클럽 #내일배움캠프(프론트엔드_React)
학습주제 : React (숙련주차 팀프로젝트 - 뉴스피드)
학습내용 : 게시글 CRUD 구현
학습일 : 2024. 6. 6.
<목차>
1. 트러블 슈팅
2. 게시글 작성(Create) 코드
3. 게시글 삭제(Delete) 코드
4. 게시글 수정(Update) 코드
1. 트러블 슈팅 (추가 보충 요함..!)
- 삭제가 왜 안 되지?
- 버튼 이슈: 상세페이지에서 게시글 수정일 경우 버튼1 : "수정 완료"/ 버튼2 : "수정 취소", 게시글 삭제일 경우 버튼1 : "삭제 완료", 버튼 2 : "삭제 취소"
- 삼항연산자를 간단하게 만들고 싶은데 어떻게 해야 되지?
2. 게시글 작성(Create) 코드
▶ 핵심 코드(요약)
const handleCreatePost = async (e) => {
e.preventDefault();
const { data, error } = await supabase.from('POSTS').insert({ title, contents: content }).select().throwOnError();
if (error) {
openModal('게시글 실패', '게시글 생성에 실패했습니다.');
navigate(`/${userId}/blog/posts`);
} else {
setPost(...data);
openModal('게시글 성공', '게시글이 생성되었습니다.');
navigate(`/${userId}/blog/posts`);
}
};
return (
<PostWrapper>
<PostTitle placeholder="게시글의 제목을 입력해주세요." value={title} onChange={(e) => setTitle(e.target.value)} />
<PostContent
placeholder="게시글의 내용을 입력해주세요."
value={content}
onChange={(e) => setContent(e.target.value)}
/>
<ButtonWrapper>
<PostSaveButton onClick={handleCreatePost}>저장</PostSaveButton>
<PostCancelButton onClick={() => navigate(`/${userId}/blog/posts`)}>취소</PostCancelButton>
</ButtonWrapper>
</PostWrapper>
);
};
▶ 전체 코드 (접은글)
더보기
import { useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import styled from 'styled-components';
import supabase from '../../config/supabase.js';
import { useModal } from '../../contexts/popup.context.jsx';
const PostCreatingPage = () => {
const [post, setPost] = useState({
id: '',
title: '',
contents: '',
});
const [title, setTitle] = useState('');
const [content, setContent] = useState('');
const { userId } = useParams();
const navigate = useNavigate();
const { openModal } = useModal();
const handleCreatePost = async (e) => {
e.preventDefault();
const { data, error } = await supabase.from('POSTS').insert({ title, contents: content }).select().throwOnError();
if (error) {
openModal('게시글 실패', '게시글 생성에 실패했습니다.');
navigate(`/${userId}/blog/posts`);
} else {
setPost(...data);
openModal('게시글 성공', '게시글이 생성되었습니다.');
navigate(`/${userId}/blog/posts`);
}
};
return (
<PostWrapper>
<PostTitle placeholder="게시글의 제목을 입력해주세요." value={title} onChange={(e) => setTitle(e.target.value)} />
<PostContent
placeholder="게시글의 내용을 입력해주세요."
value={content}
onChange={(e) => setContent(e.target.value)}
/>
<ButtonWrapper>
<PostSaveButton onClick={handleCreatePost}>저장</PostSaveButton>
<PostCancelButton onClick={() => navigate(`/${userId}/blog/posts`)}>취소</PostCancelButton>
</ButtonWrapper>
</PostWrapper>
);
};
export default PostCreatingPage;
const PostWrapper = styled.div`
width: 100%;
height: auto;
display: flex;
flex-direction: column;
flex-wrap: wrap;
align-items: center;
margin-top: 50px;
gap: 30px;
`;
const PostTitle = styled.input`
width: 90%;
font-family: 'Inter';
font-style: normal;
font-weight: 700;
font-size: 30px;
line-height: 50px;
text-align: center;
color: #000000;
border: 1px solid #d2dade;
border-radius: 10px;
`;
const PostContent = styled.textarea`
width: 90%;
min-height: 200px;
font-family: 'Inter';
font-style: normal;
font-weight: 500;
font-size: 20px;
line-height: 50px;
text-align: center;
color: #000000;
border: 1px solid #d2dade;
border-radius: 10px;
background-color: #ffffff;
`;
const ButtonWrapper = styled.div`
width: 90%;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
gap: 20px;
`;
const PostSaveButton = styled.button`
width: 200px;
height: 35px;
padding: 0 10px;
font-family: 'Inter';
font-style: normal;
font-weight: 700;
font-size: 20px;
background-color: #ff6077;
border: 1px solid #ff6077;
border-radius: 10px;
color: white;
box-sizing: border-box;
transition-duration: 250ms;
&:hover {
cursor: pointer;
transform: scale(1.03);
transition: all 0.1s ease;
box-shadow: 0px 10px 10px 0px rgba(0, 0, 0, 0.1);
}
`;
const PostCancelButton = styled.button`
width: 200px;
height: 35px;
padding: 0 10px;
min-width: 100px;
font-family: 'Inter';
font-style: normal;
font-weight: 700;
font-size: 20px;
background-color: white;
border: 1px solid #3aa6b9;
border-radius: 10px;
color: #3aa6b9;
box-sizing: border-box;
transition-duration: 250ms;
&:hover {
cursor: pointer;
transform: scale(1.03);
transition: all 0.1s ease;
box-shadow: 0px 10px 10px 0px rgba(0, 0, 0, 0.1);
}
`;
3. 게시글 삭제(Delete) 코드
▶ 핵심 코드(요약)
const handleDeletePost = async () => {
if (window.confirm('정말로 게시글을 삭제하시겠습니까?')) {
const { error } = await supabase.from('POSTS').delete().eq('id', postId);
if (error) {
alert('게시글 삭제에 실패했습니다.');
navigate(`/${userId}/blog/posts`);
}
alert('게시글이 삭제되었습니다.');
navigate(`/${userId}/blog/posts`);
}
};
▶ 전체 코드 (아래 4번 게시글 수정 코드의 전체 코드(접은글) 참고)
4. 게시글 수정(Update) 코드
▶ 핵심 코드(요약)
const handleTogglePost = async () => {
const { data, error } = await supabase
.from('POSTS')
.update({
title: title,
contents: content,
})
.eq('id', postId)
.select();
if (error) {
alert('게시글 업데이트에 실패했습니다.');
navigate(`/${userId}/blog/posts`);
}
setPost(...data);
setIsEditing(false);
alert('게시글의 수정이 완료되었습니다.');
navigate(`/${userId}/blog/posts`);
};
▶ 전체 코드 (접은글)
더보기
import { useEffect, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import styled from 'styled-components';
import supabase from '../../config/supabase.js';
import { useUser } from '../../contexts/login.context.jsx';
import formatDate, { DATE_FORMATS } from '../../utils/dateFormatUtils.js';
function PostDetailPage() {
const { userData } = useUser();
const { postId, userId } = useParams();
const [post, setPost] = useState({
id: postId,
title: '',
contents: '',
});
const [isEditing, setIsEditing] = useState(false);
const [title, setTitle] = useState('');
const [content, setContent] = useState('');
const navigate = useNavigate();
useEffect(() => {
supabase
.from('POSTS')
.select('*')
.eq('id', postId)
.then((response) => {
const { data, error } = response;
if (error) {
alert('오류가 발생했습니다.');
navigate(`/${userId}/blog/posts`);
}
const dbPost = data.find((dbData) => dbData.id === postId);
setPost({
...dbPost,
created_at: formatDate(new Date(dbPost.created_at), DATE_FORMATS.KOREAN),
});
});
}, [postId]);
const handleTogglePost = async () => {
const { data, error } = await supabase
.from('POSTS')
.update({
title: title,
contents: content,
})
.eq('id', postId)
.select();
if (error) {
alert('게시글 업데이트에 실패했습니다.');
navigate(`/${userId}/blog/posts`);
}
setPost(...data);
setIsEditing(false);
alert('게시글의 수정이 완료되었습니다.');
navigate(`/${userId}/blog/posts`);
};
const handleDeletePost = async () => {
if (window.confirm('정말로 게시글을 삭제하시겠습니까?')) {
const { error } = await supabase.from('POSTS').delete().eq('id', postId);
if (error) {
alert('게시글 삭제에 실패했습니다.');
navigate(`/${userId}/blog/posts`);
}
alert('게시글이 삭제되었습니다.');
navigate(`/${userId}/blog/posts`);
}
};
return (
<>
<PostWrapper>
<PostHeaderContainer>
{isEditing ? (
<PostTitle placeholder={post.title} onChange={(e) => setTitle(e.target.value)} />
) : (
<PostTitleP>{post.title}</PostTitleP>
)}
<PostCreatedAt> {post.created_at}</PostCreatedAt>
<PostTitleLine />
</PostHeaderContainer>
{isEditing ? (
<PostContent placeholder={post.contents} onChange={(e) => setContent(e.target.value)} />
) : (
<PostContentP>{post.contents}</PostContentP>
)}
<ButtonWrapper>
{userData.userId !== userId ? (
''
) : (
<PostSaveButton
onClick={() => {
isEditing ? handleTogglePost() : setIsEditing(true);
}}
>
{isEditing ? '수정 완료' : '수정'}
</PostSaveButton>
)}
{userData.userId !== userId ? (
''
) : isEditing ? (
<PostCancelButton onClick={() => navigate(`/${userId}/blog/posts`)}>수정 취소</PostCancelButton>
) : (
<PostCancelButton onClick={handleDeletePost}>삭제</PostCancelButton>
)}
</ButtonWrapper>
</PostWrapper>
</>
);
}
export default PostDetailPage;
const PostWrapper = styled.div`
width: 100%;
height: auto;
display: flex;
flex-direction: column;
flex-wrap: wrap;
align-items: center;
margin-top: 50px;
gap: 30px;
`;
const PostHeaderContainer = styled.div`
width: 100%;
min-height: 100px;
`;
const PostTitle = styled.input`
width: 90%;
font-family: 'Inter';
font-style: normal;
font-weight: 700;
font-size: 30px;
line-height: 50px;
text-align: center;
color: #000000;
border: 1px solid #d2dade;
border-radius: 10px;
margin: 0 auto;
`;
const PostTitleP = styled.p`
font-family: 'Inter';
font-style: normal;
font-weight: 700;
font-size: 30px;
line-height: 50px;
text-align: center;
color: #000000;
`;
const PostTitleLine = styled.div`
width: 50px;
margin: 0 auto;
border: 1px solid #ff6077;
`;
const PostCreatedAt = styled.div`
font-family: 'Inter';
font-style: normal;
font-weight: 100;
font-size: 15px;
line-height: 50px;
text-align: center;
`;
const PostContent = styled.textarea`
width: 90%;
min-height: 200px;
font-family: 'Inter';
font-style: normal;
font-weight: 500;
font-size: 20px;
line-height: 50px;
text-align: center;
color: #000000;
border: 1px solid #d2dade;
border-radius: 10px;
background-color: #ffffff;
`;
const PostContentP = styled.p`
width: 90%;
min-height: 200px;
font-family: 'Inter';
font-style: normal;
font-weight: 500;
font-size: 20px;
line-height: 50px;
text-align: center;
color: #000000;
background-color: #ffffff;
`;
const ButtonWrapper = styled.div`
width: 90%;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
gap: 20px;
`;
const PostSaveButton = styled.button`
min-width: 160px;
height: 35px;
font-family: 'Inter';
font-style: normal;
font-weight: 700;
font-size: 20px;
background-color: #ff6077;
border: 1px solid #ff6077;
border-radius: 10px;
color: white;
&:hover {
cursor: pointer;
transform: scale(1.03);
transition: all 0.1s ease;
box-shadow: 0px 10px 10px 0px rgba(0, 0, 0, 0.1);
}
`;
const PostCancelButton = styled.button`
min-width: 160px;
height: 35px;
font-family: 'Inter';
font-style: normal;
font-weight: 700;
font-size: 20px;
background-color: white;
border: 1px solid #ff6077;
border-radius: 10px;
color: #ff6077;
&:hover {
cursor: pointer;
transform: scale(1.03);
transition: all 0.1s ease;
box-shadow: 0px 10px 10px 0px rgba(0, 0, 0, 0.1);
}
`;
'개발 > React' 카테고리의 다른 글
🌐비동기 프로그래밍 - Promise, HTTP, json-server (2024. 6. 11.) (0) | 2024.06.11 |
---|---|
[팀프로젝트 회고] 코딩 2개월만에 프로젝트를 완성해보다! (2024. 6. 7.) (0) | 2024.06.10 |
uuid 설치 (0) | 2024.06.05 |
[Redux] Action Values, Action Creators (0) | 2024.06.04 |
🌐Redux 3 - Action Creators, Payload, Ducks 패턴 (0) | 2024.06.04 |