FRONTENDSCHOOL6 / ready-act

멋쟁이사자처럼 파이널프로젝트 16조
MIT License
0 stars 4 forks source link

[R09M App] CreateRoom 페이지 - Creator form 이슈 #35

Closed jellyjoji closed 1 year ago

jellyjoji commented 1 year ago

내용

form data 에서 Creator 생성자 정보를 불러와서 표기해주려고 합니다.

Creator.jsx

import { AppContext } from '@/App';
// import FormInput from '../../components/FormInput';
import { useContext, useState, useEffect } from 'react';
import { useParams } from 'react-router-dom';
import { pb } from '@/api/pocketbase';

function Creator() {
  const { updateCreateRoomForm } = useContext(AppContext);
  const [idData, setIdData] = useState([]);
  const [productIdData, setProductIdData] = useState([]);
  const { id } = useParams();

  // localStorage에서 아이디 정보를 가져오는 함수
  const localStorageId = JSON.parse(localStorage.getItem('pocketbase_auth')).model.id;
  // console.log(localStorageId);

  useEffect(() => {
    async function readUserId() {
      try {
        // 포켓 호스트에서 아이디 정보를 가져오는 함수
        const pocketHostId = await pb.collection('users').getFullList();
        // console.log(pocketHostId);
        // localStorageId 과 pocketHostId 두개를 비교하여 일치하는것만 setIdData 에 주입하여 렌더
        pocketHostId.filter((idList) => {
          // console.log(idList.id);
          if (idList.id === localStorageId) {
            setIdData(idList);

          }
        })

      }
      catch (error) {
        if (!(error instanceof ClientResponseError)) {
          console.error(error);
        }
      }

    }

    async function readProductId() {
      try {
        const productId = await pb.collection('products').getFullList({
          expand: 'creator',
          filter: `creator.id="${localStorageId}"`,
        });

        // console.log(productId);
        setProductIdData(productId);

      } catch (error) {
        if (!(error instanceof ClientResponseError)) {
          console.error(error);
        }
      }
    }

    readUserId();
    readProductId()

    updateCreateRoomForm('creator', idData.name);
    console.log(idData.name);
    // console.log(idData);
  }, [idData.name])

  // 이제 이 비교된 결과값을 넣는 Creator input 창이 필요 
  // creator input 의 값을 context api 에서 뽑아서 사용 app context form 에 넣어서 상태 관리
  return (
    <>

      <div readOnly label="생성자" placeholder="생성자" labelClassName="creator" inputClassName="w-full defaultInput">{idData.name}</div>

    </>
  )
}
export default Creator;

※ 댓글에 이슈 해결 완료 후 결과 또는 해결 과정 이미지 첨부

jellyjoji commented 1 year ago

지난 번 알려드렸던 실수를 다시 반복하지 않는 개발자가 되길 바랍니다. 🥹

문제 해결

문제 해결한 결과

먼저 문제가 해결된 결과를 영상으로 확인하세요. https://github.com/FRONTENDSCHOOL6/ready-act/assets/74365275/abe430a5-513a-43ee-9f09-7ce64614b1aa

지난 번에도 알려드렸듯이 로컬 스토리지에서 데이터를 읽어오는 것은 비동기 처리가 필요합니다. 그러므로 useState 훅을 사용해 지연된 처리가 필요하다는 점 이번엔 꼭 기억해주세요!

idData가 서버로부터 응답받은 결과로 업데이트가 되면 실행되는 이펙트 함수를 설정하고, 조건에 따라 creator 필드 값을 설정하도록 작성합니다. 아래 작성된 코드의 주석을 참고하세요.

Creator.jsx

function Creator() {
    // ...

  // localStorage에서 아이디 정보를 가져오는 함수
  const [localStorageId] = useState(
    () => JSON.parse(localStorage.getItem('pocketbase_auth')).model.id
  );

  const [idData, setIdData] = useState({});

    // idData 상태가 업데이트 되면 실행되는 이펙트 콜백 함수
  useEffect(() => {
    if (idData.id) {
            // idData 값이 서버로부터 가져와 업데이트 되면
            // creator 폼 필드를 { id, name } 객체 값으로 설정합니다.
      updateCreateRoomForm('creator', { id: idData.id, name: idData.name });
    }
  }, [idData]);

    // ...

  return (
    <>
      <div
        readOnly
        label="생성자"
        placeholder="생성자"
        labelClassName="creator"
        inputClassName="w-full defaultInput"
      >
        {idData.name}
      </div>
    </>
  );
}

“방 만들기” 할 때 서버에 전송되는 creator 필드 값은 creatorValue.id 여야 합니다. 관계된 users 콜렉션의 레코드 ID가 저장되어야 하기 때문입니다.

CreateRoom.jsx

const handleCreate = async (e) => {
  e.preventDefault();

    // ...
  const creatorValue = createRoomForm.creator;

  const data = new FormData();

    // ...

    // creator 객체의 id 값을 'creator'로 설정
  data.append('creator', creatorValue.id);

  // ...
};
jellyjoji commented 1 year ago

해결된 코드 첨부

Creator.jsx


import { AppContext } from '@/App';
import FormInput from '../../components/FormInput';
import { useContext, useState, useEffect } from 'react';
import { useParams } from 'react-router-dom';
import { pb } from '@/api/pocketbase';
import { ClientResponseError } from 'pocketbase';

function Creator() {
  const { updateCreateRoomForm } = useContext(AppContext);
  const [productIdData, setProductIdData] = useState("");
  const { id } = useParams();
  // localStorage에서 아이디 정보를 가져오는 함수
  // 로컬 스토리지에서 데이터를 읽어오는 것은 비동기 처리가 필요합니다. 그러므로 useState 훅을 사용해 지연된 처리가 필요
  const [localStorageId] = useState(() => JSON.parse(localStorage.getItem('pocketbase_auth')).model.id);

  const [idData, setIdData] = useState({});

  // idData 상태가 업데이트 되면 실행되는 이펙트 콜백 함수
  useEffect(() => {
    if (idData.id) {
      // idData 값이 서버로부터 가져와 업데이트 되면
      // creator 폼 필드를 { id, name } 객체 값으로 설정합니다.
      updateCreateRoomForm('creator', { id: idData.id, name: idData.name });
    }
  }, [idData])

  useEffect(() => {
    async function readUserId() {
      try {
        // 포켓 호스트에서 아이디 정보를 가져오는 함수
        const pocketHostId = await pb.collection('users').getFullList();

        // localStorageId 과 pocketHostId 두개를 비교하여 일치하는것만 setIdData 에 주입하여 렌더
        pocketHostId.filter((idList) => {
          if (idList.id === localStorageId) {
            setIdData(idList);

          }
        })

      }
      catch (error) {
        if (!(error instanceof ClientResponseError)) {
          console.error(error);
        }
      }

    }

    async function readProductId() {
      try {
        const productId = await pb.collection('products').getFullList({
          expand: 'creator',
          filter: `creator.id="${localStorageId}"`,
        });

        setProductIdData(productId);

      } catch (error) {
        if (!(error instanceof ClientResponseError)) {
          console.error(error);
        }
      }
    }

    readUserId();
    readProductId()

  }, [])

  // 이제 이 비교된 결과값을 넣는 Creator input 창이 필요 
  // creator input 의 값을 context api 에서 뽑아서 사용 app context form 에 넣어서 상태 관리
  return (
    <>
      <FormInput readOnly value={idData.name || ''} label="생성자" type="text" name="creator" placeholder="생성자 정보" inputClassName="w-full defaultInput" />
    </>
  )
}
export default Creator;

CreateRoom.jsx

import { AppContext } from '@/App';
import { pb } from '@/api/pocketbase';
import Button from '@/components/Button';
import FormInput from '@/components/FormInput';
import CreateHeader from '@/layout/CreateHeader';
import CategoryDropdown from '@/parts/create/CategoryDropdown';
import ContentTextarea from '@/parts/create/ContentTextarea';
import DatePicker from '@/parts/create/DatePicker';
import FileUpload from '@/parts/create/FileUpload';
import MeetingPoint from '@/parts/create/MeetingPoint';
import ParticipateCounter from '@/parts/create/ParticipateCounter';
import PaymentToggleButton from '@/parts/create/PaymentToggleButton';
import Status from '@/parts/create/Status';
import { ClientResponseError } from 'pocketbase';
import { useContext, useRef, useState } from 'react';
import { Helmet } from 'react-helmet-async';
import Creator from '@/parts/create/Creator';

function CreateRoom() {
  const { createRoomForm, updateCreateRoomForm } = useContext(AppContext);

  const formRef = useRef(null);
  const categoryRef = useRef(null);
  const titleRef = useRef(null);
  const contentRef = useRef(null);
  const priceRef = useRef(null);
  const dateRef = useRef(null);
  const paymentRef = useRef(null);
  const ParticipateCounterRef = useRef(null);
  const uploadImageRef = useRef(null);
  const statusRef = useRef(null);

  const handleCreate = async (e) => {
    e.preventDefault();

    const categoryValue = categoryRef.current.value;
    const titleValue = titleRef.current.value;
    const contentValue = contentRef.current.value;
    const priceValue = priceRef.current.value;
    const dateValue = dateRef.current.value;

    const paymentValue = paymentRef.current.dataset.payment;
    const ParticipateCounterValue = Number(
      ParticipateCounterRef.current.textContent
    );

    const meetingPointValue = createRoomForm.meetingPoint;
    const creatorValue = createRoomForm.creator.id;

    const uploadImageValue = uploadImageRef.current.files[0];
    const statusValue = statusRef.current.value;

    const data = new FormData();

    data.append('category', categoryValue);
    data.append('title', titleValue);
    data.append('content', contentValue);
    data.append('price', priceValue);
    data.append('pickup', dateValue);
    data.append('payment', paymentValue);
    data.append('participateNumber', ParticipateCounterValue);
    data.append('meetingPoint', meetingPointValue);
    data.append('creator', creatorValue);
    data.append('participate', creatorValue);
    if (uploadImageValue) {
      data.append('uploadImage', uploadImageValue);
    }
    data.append("status", statusValue);

    for (const [key, value] of data.entries()) {
      console.log(key, value);
    }

    // return
    try {
      await pb.collection('products').create(data);

      // navigate('/products');

    } catch (error) {
      if (!(error instanceof ClientResponseError)) {
        console.error(error);
      }
    }
  }

  return (
    <>

      <Helmet>
        <title>방만들기</title>
      </Helmet>

      <div >
        <CreateHeader />

        <form
          encType="multipart/form-data"
          ref={formRef}
          onSubmit={handleCreate}
        >
          <div className="flex flex-col gap-4 p-4 relative"
          >

            <CategoryDropdown
              ref={categoryRef}
              title="카테고리"
              className="w-full defaultInput"
            />
            <FormInput
              ref={titleRef}
              type="text"
              placeholder="상품명을 입력해주세요."
              labelClassName="product name"
              inputClassName="defaultInput w-full"
              label="상품명"
            />
            <FormInput
              ref={priceRef}
              type="number"
              placeholder="0원"
              labelClassName="product price"
              inputClassName="defaultInput w-full"
              label="상품 가격"
            />
            <ContentTextarea
              ref={contentRef}
              title="내용"
              placeholder="공구 모임 주요내용을 알려주세요."
              className="w-full defaultInput"
              labelClassName="product content"
            />

            <DatePicker
              ref={dateRef}
              title="픽업 날짜"
              className="w-full defaultInput"
              labelClassName="date Picker"
            />

            <Status
              ref={statusRef}
              title="상태"
              className="w-full defaultInput "
              labelClassName="status"
            />

            <Creator />

            <PaymentToggleButton
              ref={paymentRef}
              title="정산 방법"
              labelClassName="payment"
            />

            <ParticipateCounter ref={ParticipateCounterRef} title="인원" />

            <MeetingPoint title="만날 장소" />

            <FileUpload
              ref={uploadImageRef}
              title="파일 업로드"
              className="bg-[#EBF8E8] p-4 rounded-lg text-primary-500"
            />
          </div>
          <div className="bg-white fixed bottom-0 max-w-xl w-full p-4 drop-shadow-2xl">
            <Button type="submit" className="activeButton lgFontButton w-full ">
              방 만들기
            </Button>
          </div>
        </form>
      </div>
    </>
  );
}

export default CreateRoom;