[Stack Overflow] ๋‹ต๋ณ€ CRUD

hznยท2023๋…„ 1์›” 1์ผ
0

PROJECT๐Ÿ

๋ชฉ๋ก ๋ณด๊ธฐ
6/24
post-thumbnail

1. ์งˆ๋ฌธ & ๋‹ต๋ณ€ ๋ฐ›์•„์˜ค๊ธฐ (GET)

๐Ÿ˜€ ๋ฐฑ์—”๋“œ์—์„œ ์งˆ๋ฌธ ๋ฐ์ดํ„ฐ ์•ˆ์— ๋‹ต๋ณ€ ๋ฐ์ดํ„ฐ๊ฐ€ ํฌํ•จ๋œ ํ˜•ํƒœ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ฃผ๊ธฐ๋กœ ํ–ˆ๋‹ค.
๐Ÿ‘‰๐Ÿฝ ๊ฐ ํŽ˜์ด์ง€์— ํ•ด๋‹นํ•˜๋Š” ์งˆ๋ฌธ(+๋‹ต๋ณ€) ๋ฐ์ดํ„ฐ ๋ฐ›์•„์˜ค๊ธฐ
๐Ÿ‘‰๐Ÿฝ ๋‹ต๋ณ€ ๋ถ€๋ถ„์€ ์งˆ๋ฌธ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์™€์„œ ๊ฐ€๊ณตํ•ด์„œ ์“ฐ๋Š” ๊ฑธ๋กœ..!

๋ฐ์ดํ„ฐ ํ˜•ํƒœ

{
  "questions": [
    {
      "id": 1,
      "questionId": 1,
      "title": "๐ŸŒˆ Suggestion for Automatically Match Data in MysQL",
      "content": "๐ŸŒŸ I have limited access to the MySQL database, I just can see a view called customer contains customer_id, name, and their location",
      "question_tag": [
        "python",
        "mysql",
        "database",
        "jupyter-notebook"
      ],
      "totalRecommend": 3,
      "createdAt": "Dec 12 at 6:48",
      "member_id": "Arthur",
      "answers": [
        {
          "answerId": 1,
          "content": "๐Ÿฆ„ This is a very stacked question with lots of steps needed to achieve what you want. So let's dive straight in! First, we should read the data frames from your (uncleaned) customer database and your location database",
          "recommend": 1,
          "createdAt": "Dec 15 at 12:06",
          "choose": false,
          "member_id": "Luke",
          "questionId": 1
        },
        {
          "answerId": 2,
          "content": "๐Ÿ”ฅ I don't know if I understood correctly here.. [...] I mean if in the raw data there is punctuation or number or typo, it will automatically clean [...] You need some sort of validation method here, you cannot achieve that directly on the database, you need to handle it in your logic before the rows insertion. In these cases, the best solution is to prepare a picklist (multiple choice) from which end users can choose the right values. A free text input will always be error prone.If the multiple choiceis not an applicable solution in your case, then you need to put in place a list of validation rules but you need to think how to prevent every possible issue.",
          "recommend": 4,
          "createdAt": "Dec 15 at 12:06",
          "choose": false,
          "member_id": "Denis B.",
          "questionId": 1
        }
      ]
    },
...

DetailPage.js

  • DetailPage.js์—์„œ ๋ฐ์ดํ„ฐ ๋ฐ›์•„์™€์„œ ํ•„์š”ํ•œ ์ปดํฌ๋„ŒํŠธ์— props๋กœ ๋„˜๊ฒจ์ค€๋‹ค.
useEffect(() => {
    async function request() {
      const response = await axios.get( // ํ•ด๋‹น ์งˆ๋ฌธ ๋ฐ์ดํ„ฐ ๋ฐ›์•„์˜ค๊ธฐ
	`${process.env.REACT_APP_API_URL}/api/questions/${questionId}`);
      const { data } = response;
      console.log(data);
      setQuestionData(data);
    }
    request();
  }, []);
...
return (
    <>
      {questionData && (
        <DetailContainer>
          <Title data={questionData} /> // ํ•ด๋‹น ์งˆ๋ฌธ ๋ฐ์ดํ„ฐ props๋กœ ๋„˜๊ฒจ์ฃผ๊ธฐ
          <hr />
          <ContentsAndSideBox>
            <ContentsContainer>
              <Contents data={questionData} /> //ํ•ด๋‹น ์งˆ๋ฌธ ๋ฐ์ดํ„ฐ props๋กœ ๋„˜๊ฒจ์ฃผ๊ธฐ
            </ContentsContainer>
            <SideBox>
              <QuestionsSub />
            </SideBox>
          </ContentsAndSideBox>
        </DetailContainer>
      )}
    </>
  );
}

2. ๋‹ต๋ณ€ ์ง€์šฐ๊ธฐ (DELETE)

  • Delete ๋ฒ„ํŠผ ๋ˆ„๋ฅด๋ฉด ๋‹ต๋ณ€ ์ง€์›Œ์ง€๋„๋ก ํ•˜๊ธฐ

AnswerList.js

export default function AnswerList({ isMyQuestion, data }) {
...
function handleAnswerDelete(e, answerId) {
    const newAnswerList = data.filter((el) => el.answerId !== answerId); // ํ•ด๋‹น ๋‹ต๋ณ€์ด ๋น ์ง„ ์ƒˆ๋กœ์šด ๋‹ต๋ณ€ ๋ฆฌ์ŠคํŠธ ๋งŒ๋“ค๊ธฐ
    e.preventDefault();

    async function request() {
      await axios.patch( // patch ์š”์ฒญ ๋ณด๋‚ด๊ธฐ
        `${process.env.REACT_APP_API_URL}/api/questions/${questionId}`,
        { answers: newAnswerList }
      );
      window.location.reload();
    }
    request();
  }
...
return (
  ...
   <button onClick={(e) => handleAnswerDelete<(e, answer.answerId)}className="menu">Delete</button>
  • ์š”์ฒญ์„ ํ•ด๋‹น ์งˆ๋ฌธ์— ๋Œ€ํ•œ api๋กœ ํ•ด์•ผ ํ•˜๊ณ , ํ•ด๋‹น ์งˆ๋ฌธ ๋ฐ์ดํ„ฐ์˜ ํ•œ ๋ถ€๋ถ„(์งˆ๋ฌธ์˜ ๋‹ต๋ณ€์˜ ์ผ๋ถ€)๋งŒ ์ˆ˜์ •(์‚ญ์ œ)ํ•˜๋Š” ๊ฒƒ์ด๋ฏ€๋กœ patch๋ฅผ ์‚ฌ์šฉ
  • filter๋ฅผ ์ด์šฉํ•ด ํ•ด๋‹น ๋‹ต๋ณ€์ด ๋น ์ง„ ์ƒˆ๋กœ์šด ๋‹ต๋ณ€ ๋ฆฌ์ŠคํŠธ(newAnswerList) ๋งŒ๋“ค๊ธฐ
  • axios.patch์˜ ๋‘ ๋ฒˆ์งธ ์ธ์ž๋กœ patchํ•  ๋ถ€๋ถ„์„ ๊ฐ์ฒด ํ˜•ํƒœ๋กœ ๋„ฃ์–ด์ฃผ๊ธฐ
    (์—ฌ๊ธฐ์„œ๋Š” answers ํ•ญ๋ชฉ์˜ ๊ฐ’์„ ์ƒˆ๋กœ ๋งŒ๋“  newAnswerList๋กœ ๋ฐ”๊พธ๋Š” ๊ฒƒ)

3. ๋‹ต๋ณ€ ์ž‘์„ฑํ•˜๊ธฐ (POST)

AnswerSubmit.js

export default function AnswerSubmit({ data }) {
  const [answer, answerBind] = useInput(); // ์ปค์Šคํ…€ ํ›…(useInput) ์ด์šฉ

  const newAnswer = { // ์ƒˆ ๋‹ต๋ณ€ ๊ฐ์ฒด ๋งŒ๋“ค๊ธฐ
    answerId: data.length + 1,
    content: answer,
    recommend: 0,
    createdAt: new Date().toLocaleDateString('ko-KR'),
    choose: false,
    member_id: 'Pika', 
    questionId: questionId,
  };

  const handleSubmit = (e) => { // SubmitBtn ํด๋ฆญ ์‹œ ์‹คํ–‰
    e.preventDefault();

      async function request() {
        await axios.patch(
          `${process.env.REACT_APP_API_URL}/api/questions/${questionId}`,
          { answers: [...data, newAnswer] }
        );
        window.location.reload();
      }
      request();
  };
...
return (
    <div>
      <AnswerFormHeader>Your Answer</AnswerFormHeader>
      <form onSubmit={handleSubmit}>
        <Textarea required {...answerBind} placeholder=""></Textarea>
        <Guideline>
          Thanks for contributing an answer to Stack Overflow!
          <br />
...
		 <SubmitBtn>Post Your Answer</SubmitBtn>
       </form>
  • ์—ญ์‹œ ์š”์ฒญ์„ ํ•ด๋‹น ์งˆ๋ฌธ์— ๋Œ€ํ•œ api๋กœ ํ•ด์•ผ ํ•˜๊ณ , ํ•ด๋‹น ์งˆ๋ฌธ ๋ฐ์ดํ„ฐ์˜ ํ•œ ๋ถ€๋ถ„(์งˆ๋ฌธ์˜ ๋‹ต๋ณ€์˜ ์ผ๋ถ€)๋งŒ ์ˆ˜์ •(์ถ”๊ฐ€)ํ•˜๋Š” ๊ฒƒ์ด๋ฏ€๋กœ patch๋ฅผ ์‚ฌ์šฉ
  • ์ถ”๊ฐ€ํ•  ์ƒˆ ๋‹ต๋ณ€ ๊ฐ์ฒด(newAnswer) ๋งŒ๋“ค๊ธฐ
  • axios.patch์˜ ๋‘ ๋ฒˆ์งธ ์ธ์ž๋กœ patchํ•  ๋ถ€๋ถ„์„ ๊ฐ์ฒด ํ˜•ํƒœ๋กœ ๋„ฃ์–ด์ฃผ๊ธฐ
    (์—ฌ๊ธฐ์„œ๋Š” answers ํ•ญ๋ชฉ์˜ ๊ฐ’(๋ฐฐ์—ด)์— ์ƒˆ ๋‹ต๋ณ€(newAnswer)์„ ์š”์†Œ๋กœ ์ถ”๊ฐ€ํ•œ ๊ฒƒ)
  • input ์ฐฝ์— ์ž…๋ ฅ๋œ ๊ฐ’ ๋“ฑ์„ ๊ฐ€์ ธ์˜ค๋Š”๋ฐ ์ปค์Šคํ…€ ํ›…(useInput.js) ์ด์šฉ

useInput.js

import { useState } from 'react';

const useInput = () => {
  const [inputValue, setInputValue] = useState('');

  const bind = {
    value: inputValue,
    onChange: (e) => setInputValue(e.target.value),
  };

  return [inputValue, bind];
};

export default useInput;

4. ๋‹ต๋ณ€ ์ˆ˜์ •ํ•˜๊ธฐ (PATCH)

  • Edit ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ ๋‹ต๋ณ€ ์ˆ˜์ • ํŽ˜์ด์ง€๋กœ ์ด๋™
 <Link to={`/posts/${questionId}/edit/${answer.answerId}`}>
	<button className="menu">Edit</button>
 </Link>

  • patch ์‚ฌ์šฉ
  • ์ˆ˜์ •ํ•  ๋‹ต๋ณ€๋งŒ ๋บ€ ๋‹ต๋ณ€ ๋ฆฌ์ŠคํŠธ(editedAnswerList)์™€ ์ˆ˜์ •๋œ ๋‹ต๋ณ€ ๊ฐ์ฒด(editedAnswer) ๋งŒ๋“ค๊ธฐ
  • axios.patch์˜ ๋‘ ๋ฒˆ์งธ ์ธ์ž๋กœ patchํ•  ๋ถ€๋ถ„์„ ๊ฐ์ฒด ํ˜•ํƒœ๋กœ ๋„ฃ์–ด์ฃผ๊ธฐ
    (์—ฌ๊ธฐ์„œ๋Š” answers ํ•ญ๋ชฉ์˜ ๊ฐ’(๋ฐฐ์—ด)์— ์ˆ˜์ •๋œ ๋‹ต๋ณ€ ๋ฆฌ์ŠคํŠธ์˜ ์š”์†Œ๋“ค๊ณผ ์ˆ˜์ •๋œ ๋‹ต๋ณ€ ๊ฐ์ฒด๋ฅผ ๋„ฃ์–ด์คŒ)
  • input ์ฐฝ์— ์ž…๋ ฅ๋œ ๊ฐ’ ๋“ฑ์„ ๊ฐ€์ ธ์˜ค๋Š”๋ฐ ์ปค์Šคํ…€ ํ›…(useInput.js) ์ด์šฉ

EditAnswerPage.js

export default function EditAnswerPage() {
  const [edited, editedBind] = useInput(); // ์ปค์Šคํ…€ ํ›… (useInput ์‚ฌ์šฉ)

  const handleEditSubmit = (e, answerId) => { // SubmitBtn ํด๋ฆญ ์‹œ ์‹คํ–‰
    // ์ˆ˜์ •ํ•  ๋‹ต๋ณ€๋งŒ ๋บ€ ๋‹ต๋ณ€ ๋ฆฌ์ŠคํŠธ ๋งŒ๋“ค๊ธฐ
    const editedAnswerList = answerData.filter(
      (el) => el.answerId !== Number(answerId)
    ); 

  // ์ˆ˜์ •๋œ ๋‹ต๋ณ€ ๋งŒ๋“ค๊ธฐ
    const editedAnswer = {
      answerId: Number(answerId), // params๋กœ ๋ฐ›์•„์˜ด
      content: edited, // useInput ์ด์šฉํ•ด์„œ ๋ฐ›์•„์˜ด
      recommend: answerData[answerId - 1].recommend,
      createdAt: answerData[answerId - 1].createdAt,
      choose: answerData[answerId - 1].choose,
      member_id: answerData[answerId - 1].member_id,
      questionId: Number(questionId), // params๋กœ ๋ฐ›์•„์˜ด
    };

    e.preventDefault();

    async function request() {
      await axios.patch(
      `${process.env.REACT_APP_API_URL}/questions/${questionId}`, 
  { answers: [...editedAnswerList, editedAnswer] }
      );
      navigate(`/questions/${questionId}`);
    }
    request();
  };

  return (
    <>
      {answerData && (
     ...
            <form onSubmit={(e) => handleEditSubmit(e, answerId)}>
              <Textarea
                required
                {...editedBind}
                placeholder={answerData[answerId - 1].content}></Textarea> 
...
          </Guideline>
              <SubmitBtn>Save edits</SubmitBtn>
            </form>

๐Ÿฅ ๋‹ต๋ณ€ placeholder ์„ค์ •ํ•˜๊ธฐ

  • ๋‹ต๋ณ€ ์ˆ˜์ • ํŽ˜์ด์ง€ ๋“ค์–ด๊ฐ€๋ฉด placeholder๋กœ ํ•ด๋‹น ๋‹ต๋ณ€ ๋– ์žˆ๊ฒŒ ํ•˜๊ธฐ
    1) ๋‹ต๋ณ€ ์†ํ•œ ์งˆ๋ฌธ ๋ฐ์ดํ„ฐ ๋ฐ›์•„์˜ค๊ธฐ
    2) answer ๋ฐ์ดํ„ฐ id ์ˆœ์œผ๋กœ ์ •๋ ฌ
    3) placeholder๋กœ ๋ณด์—ฌ์ฃผ๊ธฐ
export default function EditAnswerPage() {
  const [answerData, setAnswerData] = useState(); // id ์ˆœ์œผ๋กœ ์ •๋ ฌ๋œ answer ๋ฐ์ดํ„ฐ
...  
  useEffect(() => {
    async function request() {
      const response = await axios.get( // 1) ๋‹ต๋ณ€ ์†ํ•œ ์งˆ๋ฌธ ๋ฐ์ดํ„ฐ ์ „์ฒด ๋ฐ›์•„์˜ค๊ธฐ
        `${process.env.REACT_APP_API_URL}/questions/${questionId}`);
      const { data } = response; 

      // 2) answer ๋ฐ์ดํ„ฐ id ์ˆœ์œผ๋กœ ์ •๋ ฌ
      const sortedAnswerData = data.answers.sort(function (a, b) {
        if (a.answerId > b.answerId) {
          return 1;
        }
        if (a.answerId < b.answerId) {
          return -1;
        }
        return 0;
      });

      setAnswerData(sortedAnswerData);
    }
    request();
  }, []);
 ...
 return (
     <Textarea  required
                {...editedBind}
                placeholder={answerData[answerId - 1].content}></Textarea> // 3) placeholder๋กœ ๋ณด์—ฌ์ฃผ๊ธฐ

0๊ฐœ์˜ ๋Œ“๊ธ€