frontendschool6 / ready-act Goto Github PK
View Code? Open in Web Editor NEW멋쟁이사자처럼 파이널프로젝트 16조
License: MIT License
멋쟁이사자처럼 파이널프로젝트 16조
License: MIT License
🛠️ 회원 정보 등록
Warning: A component is changing an uncontrolled input to be controlled. This is likely caused by the value changing from undefined to a defined value, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components
Creator.jsx 파일에 위와 같은 오류메세지가 계속 발생하여 input 요소에 문제점을 분석하였습니다.
※ 댓글에 이슈 해결 완료 후 결과 또는 해결 과정 이미지 첨부
PaymentToggleButton.jsx 에서 context 를 사용하여 데이터를 createRoomForm
으로 전달한뒤 createRoomForm.payment
로 불러와서 를 .current.dataset
로 data-payment={isToggled ? '계좌 이체' : '만나서 결제'}
를 호출하니 에러가 뜨며 dataset 을 읽어들이지 못합니다.
아래와 같이 토글버튼을 누르면 true와 false 값을 출력해내지만 dataset 을 찾지 못한다는 에러가 뜨씁니다. context 를 사용해서 불러온 createRoomForm.payment
값에 dataset 를 적용시켜 사용하려고 합니다.
.current.dataset
를 어느 부분에 적용시켜 dataset를 값으로 읽어올수가 있을까요?
import { AppContext } from '@/App';
import { useContext, useEffect, useState } from 'react';
function PaymentToggleButton({ value = "false", labelClassName, ...restProps }) {
const { updateCreateRoomForm } = useContext(AppContext);
const [isToggled, setToggled] = useState(value);
useEffect(() => {
updateCreateRoomForm('payment', isToggled);
console.log(isToggled);
}, [isToggled]);
return (
<>
<label htmlFor="payment" className={labelClassName}>
지불 방법
</label>
<div id="payment" className="bg-greenishgray-200 w-full p-2 rounded-lg">
<button
value={isToggled}
type="button"
className="w-full rounded-lg "
onClick={() => setToggled(!isToggled)}
data-payment={isToggled ? '계좌 이체' : '만나서 결제'}
{...restProps}
>
<div
className={`flex w-full ${isToggled ? 'items-center' : ''}`.trim()}
>
<div
className={`w-1/2 ${!isToggled ? 'shadow-lg bg-white rounded-lg' : ''
}`.trim()}
>
만나서 결제
</div>
<div
className={`w-1/2 ${isToggled ? 'shadow-lg bg-white rounded-lg' : ''
}`.trim()}
>
계좌 이체
</div>
</div>
</button>
</div>
</>
);
}
export default PaymentToggleButton;
다음은 토글 버튼의 UI 구현 부분에 대한 질문입니다.
<button
value={isToggled}
type="button"
className="w-full rounded-lg "
onClick={() => setToggled(!isToggled)}
data-payment={isToggled ? '계좌 이체' : '만나서 결제'}
{...restProps}
>
<div
className={`flex w-full ${isToggled ? 'items-center' : ''}`.trim()}
>
<div
className={`w-1/2 ${!isToggled ? 'shadow-lg bg-white rounded-lg' : ''
}`.trim()}
>
만나서 결제
</div>
<div
className={`w-1/2 ${isToggled ? 'shadow-lg bg-white rounded-lg' : ''
}`.trim()}
>
계좌 이체
</div>
</div>
</button>
위의 토글 버튼의 마크업을 아래의 코드와 같이 개선시켜보았습니다.
코드를 조금 간결하게 만들수있었지만 반복되는 부분을 더 줄여줄수있을지 궁금합니다.
혹시 반복되는 w-1/2 ${isToggled ? 'shadow-lg bg-white rounded-lg' : ''}
이 css 부분을 더 줄여줄수있는 연산식이 있을지 여쭤봅니다!
``jsx
<button
type="button"
className="w-full rounded-lg flex items-center"
onClick={() => setToggled(!isToggled)}
data-payment={isToggled ? '계좌 이체' : '만나서 결제'}
{...restProps}
>
<div className={w-1/2 ${!isToggled ? 'shadow-lg bg-white rounded-lg' : ''}
}>
만나서 결제
<div className={`w-1/2 ${isToggled ? 'shadow-lg bg-white rounded-lg' : ''}`}>
계좌 이체
## 참고 이미지 (선택)
![image](https://github.com/FRONTENDSCHOOL6/ready-act/assets/74365275/7f2f2063-f681-4da5-b8bc-9671b9891fcc)
`※ 댓글에 이슈 해결 완료 후 결과 또는 해결 과정 이미지 첨부`
Kakao Map 에서 찍은 주소값을 updateCreateRoomForm 에 pickUp 항목으로 저장시켰습니다, 해당 페이지를 벗어나도 vlaue 값이 기억되도록 useState(value)
를 주고 value=createRoomForm.meetingPoint}
를 CreateRoom 의 MeetingPorint컴포넌트에 적용시켰습니다. value 값을 기억해야할때 를 적용시킬 속성의 위치가 여기가 맞을까요 ? (굵은 글자 표시)
<div className="hAddr flex">
{/* 여기 */}
<span readOnly value={data || ""} id="centerAddr" className='p-4'>
{data}
</span>
Location.jsx 의 전체 코드를 공유합니다.
import arrowLeft from '@/assets/icons/arrowLeft.svg';
import { useContext, useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import './Location.module.css';
import { AppContext } from '@/App';
import Button from "../../components/Button";
const { kakao } = window;
function Location({ value }) {
const { updateCreateRoomForm } = useContext(AppContext);
const [data, setData] = useState(value);
useEffect(() => {
const mapContainer = document.getElementById('map'), // 지도를 표시할 div
mapOption = {
center: new kakao.maps.LatLng(37.57157200866145, 126.9763416696016), // 지도의 중심좌표
level: 4,
};
const map = new kakao.maps.Map(mapContainer, mapOption);
const geocoder = new kakao.maps.services.Geocoder();
const marker = new kakao.maps.Marker(),
infowindow = new kakao.maps.InfoWindow({ zindex: 1 });
searchAddrFromCoords(map.getCenter(), displayCenterInfo);
kakao.maps.event.addListener(map, 'click', function (mouseEvent) {
searchDetailAddrFromCoords(mouseEvent.latLng, function (result, status) {
if (status === kakao.maps.services.Status.OK) {
let detailAddr = !!result[0].address.address_name
? result[0].address.address_name
: '위치정보를 불러올수없음';
setData(detailAddr);
const content = '<div className="bAddr">' + detailAddr + '</div>';
marker.setPosition(mouseEvent.latLng);
marker.setMap(map);
infowindow.setContent(content);
infowindow.open(map, marker);
}
});
});
kakao.maps.event.addListener(map, 'idle', function () {
searchAddrFromCoords(map.getCenter(), displayCenterInfo);
});
function searchAddrFromCoords(coords, callback) {
geocoder.coord2RegionCode(coords.getLng(), coords.getLat(), callback);
}
function searchDetailAddrFromCoords(coords, callback) {
geocoder.coord2Address(coords.getLng(), coords.getLat(), callback);
}
function displayCenterInfo(result, status) {
if (status === kakao.maps.services.Status.OK) {
const infoDiv = document.getElementById('centerAddr');
for (let i = 0; i < result.length; i++) {
if (result[i].region_type === 'H') {
infoDiv.innerHTML = result[i].address_name;
break;
}
}
}
}
}, []);
useEffect(() => {
updateCreateRoomForm('meetingPoint', data);
}, [data]);
return (
<div className="h-full">
<div className="relative h-12">
<p className="text-center py-3">지도에서 위치 확인</p>
<Link to="/createroom">
<img src={arrowLeft} alt="뒤로 가기" className="absolute top-3" />
</Link>
</div>
<div className="map_wrap">
<div id="map" className="w-full h-[420px]"></div>
<div className="hAddr flex">
{/* <input readOnly value={data || ""} id="centerAddr" className='p-4' /> */}
<span id="centerAddr" className='p-4'>
{data}
</span>
<Link to="/createroom" className="bg-white w-full absolute max-w-xl bottom-0 p-4 drop-shadow-2xl">
<Button type="submit" className="activeButton lgFontButton w-full ">
이 위치로 설정
</Button>
</Link>
</div>
</div>
</div>
);
}
export default Location;
※ 댓글에 이슈 해결 완료 후 결과 또는 해결 과정 이미지 첨부
🛠️ 로그아웃 / 회원탈퇴 기능 구현
없음
kakao Map API 를 구현한 다음 component 로 독립적인 페이지인 Location.jsx 로 분리하는 작업을 하고있습니다. 만날 장소 등록하기를 클릭하면 새 페이지에서 열리는 방식으로 분리했을때 지도에서 새 페이지에서 지도를 눌러 얻은 결과값을 현재 작업 중인 createRoom page 로 불러와야합니다.
해당 결과값을 불러와야 하는 방식에 대해 논의드립니다.
아래와 같이 지도에 찍힌 주소 결과값을 아래와 같이 메인 createRoom 페이지의 form data 로 불러와서 입력하는 방식에 대해 질문 드립니다.
(참고)
사용한 kakao map api 첨부
https://apis.map.kakao.com/web/sample/coord2addr/
※ 댓글에 이슈 해결 완료 후 결과 또는 해결 과정 이미지 첨부
🛠️ 키워드 입력 시 키워드가 포함된 상품 렌더링
없음
정리하자면
※ 댓글에 이슈 해결 완료 후 결과 또는 해결 과정 이미지 첨부
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;
※ 댓글에 이슈 해결 완료 후 결과 또는 해결 과정 이미지 첨부
기존에 forwardRef 를 사용하여 form data 페이지와 연결시켰던 코드를 useContext를 사용하여 리팩토링 중입니다. useContext 를 사용하요 select option 에서 선택된 결과값을 useContext(AppContext) 데이터에 넣으려고 시도중입니다. 그런데 selection option 중 선택된 결과값을 setSelectedCategory 에 넣고 콘솔에 로그를 찍어보니 undefined 으로 뜹니다.
useContext 를 사용하여 선택된 결과값을 append 하려면 어느 부분을 고쳐야 할까요?
제가 표현하려는 구조는 아래 그림과 같습니다. 선택된 Category 결과값을 forwardRef 가 아닌 useContext를 사용하여 form 에 입력하려고 합니다.
CategoryDropdown.jsx
import { useId, useContext, useState, useEffect } from 'react';
import { category } from '../../data/category';
import { AppContext } from '../../App';
function CategoryDropdown({ className, title }) {
const { id } = useId();
const { updateCreateRoomForm } = useContext(AppContext);
const [selectedCategory, setSelectedCategory] = useState();
useEffect(() => {
async function readCategoryList() {
try {
const categoryList = category.map((list) => list.title);
console.log(categoryList);
console.log(setSelectedCategory);
} catch (error) {
console.error(error);
}
}
readCategoryList()
updateCreateRoomForm('category', selectedCategory);
console.log(selectedCategory);
}, []);
return (
<div>
<label htmlFor={id}>{title}</label>
<select
value={selectedCategory} onChange={(e) => setSelectedCategory(e.target.value)}
id={id}
className={className}
name="category"
defaultValue="전체"
>
{category.map((list) => (
<option key={list.title} value={list.title}>
{list.title}
</option>
))}
</select>
</div>
);
}
export default CategoryDropdown;
※ 댓글에 이슈 해결 완료 후 결과 또는 해결 과정 이미지 첨부
ParticipateCounter.jsx 안에 있는 <p ref={isRef}>{count}</p>
요소의 current.value
값을 const counterValue = counterRef.current.value
로 받아서 pockethost 에 등록시켜주려고 하고 있으나 요소가 <p>결과값</p>
형식으로 들어와버려서 데이터에 등록을 할수 없습니다. 현재 결과값만을 문자로 받아오고 싶은데 current.value
가 아닌 다른 형식으로 결과값을 받아야 할까요?
function CreateRoom() {
const formRef = useRef(null);
const counterRef = useRef(null);
const handleCreate = async (e) => {
e.preventDefault();
// 오류 : counter 에 current.value 가 안찍힌다.
const counterValue = counterRef.current.value;
console.log(counterValue);
const data = new FormData();
data.append('participateNumber', counterValue);
return ...
}
import { useState } from "react";
import { minus, plus } from '../../src/assets/icons/svg-icons';
function ParticipateCounter({ title, isRef }) {
const [count, setCount] = useState(0);
const incrementCount = () => {
setCount(count + 1);
};
const decrementCount = () => {
setCount(count - 1);
};
return (
<div>
<label>{title}</label>
<div className="flex gap-2 float-right p-2 items-center">
<button type="button" onClick={decrementCount}>
<img src={minus} alt="minus" />
</button>
<p ref={isRef}>{count}</p>
<button type="button" onClick={incrementCount}>
<img src={plus} alt="plus" />
</button>
</div>
</div>
);
};
export default ParticipateCounter;
현재 console.log(counterValue);
가 콘솔에 결과값으로 찍힙니다.
원하는 형식 : 결과값 ‘4’ 만을 받아서 pockethost 에 등록할 예정입니다.
(width="140px")
※ 댓글에 이슈 해결 완료 후 결과 또는 해결 과정 이미지 첨부
🛠️ 어떤 기능을 구현하나요?
이메일, 비밀번호 입력 시 포켓호스트에 등록된 정보와 일치 여부 확인, 로그인 된 정보 로컬스토리지에 저장
없음
포켓호스트에 등록된 정보 일치 여부 확인 및 로그인 정보 로컬스토리지에 저장
pocketHost 에 시간날짜 form 을 Create 하는 중입니다.
DatePicker 에 대한 양식은 <input type="date">
로 구현하여 form 에 맞게 전송하는데 성공했으나 포켓호스트 양식에 맞는 시간 형식을 동시에 입력할수 없는 상황입니다.
<input type="datetime-local">
형식으로 구현할 경우 2018-06-12T19:30
형식으로 입력되어 포켓호스트 양식과 일치하지 않아 form에 올라가지 않는 문제가 있습니다.
DatePicker.jsx
현재 구현한 날짜 구현 코드입니다.
import { forwardRef, useId, useState } from 'react';
// import Input from "@/components/Input";
function DatePicker({ title, className, labelClassName, ...restProps }, ref) {
const [, setDate] = useState(null);
const id = useId();
return (
<div>
<label htmlFor={id} className={labelClassName}>
{title}
</label>
<input
id={id}
ref={ref}
className={className}
type="date"
onChange={(e) => setDate(e.target.value)}
{...restProps}
/>
</div>
);
}
export default forwardRef(DatePicker);
시간 데이터를 "00:00:00" 형식으로 다음과 같은 방법 구현하여 사용하려해도 한 입력폼에 date 와 time 이 동시에 들어가야하는 문제점이 있습니다.
TimePicker.jsx
import { useState } from 'react';
function TimePicker() {
const [time, setTime] = useState('00:00:00');
const handleTimeChange = (e) => {
const inputTime = e.target.value;
// 정규식을 사용하여 입력된 값이 유효한 형식인지 확인
const timeRegex = /^([0-1][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])$/;
if (timeRegex.test(inputTime)) {
setTime(inputTime);
}
};
return (
<div>
<label htmlFor="timeInput">Time:</label>
<input
type="text"
id="timeInput"
value={time}
onChange={handleTimeChange}
/>
</div>
);
}
export default TimePicker;
아래는 데이터가 입력되어 생성되는 포켓호스트 폼 형식입니다.
※ 댓글에 이슈 해결 완료 후 결과 또는 해결 과정 이미지 첨부
아래와 같은 형태의 토글 버튼을 구현 중입니다.
둘중 하나의 버튼을 눌렀을때 선택된 결과값을 뽑아내서 form data 에 전송해야합니다.
{isToggled ?
(<div className="flex w-full items-center "><div className="w-1/2 shadow-lg bg-white rounded-lg "> 계좌 이체 </div><div className="w-1/2 "> 만나서 결제 </div></div>) :
(<div className="flex w-full">< div className="w-1/2" > 계좌 이체 </div><div className="w-1/2 shadow-lg bg-white rounded-lg">만나서 결제</div> </div>)}
PaymentToggleButton.jsx
import React, { useState, forwardRef } from "react";
function PaymentToggleButton({ labelClassName, title, }, ref) {
const [isToggled, setToggled] = useState(false);
return (
<>
<label className={labelClassName}>{title}</label>
<div className="bg-greenishgray-200 w-full p-2 rounded-lg">
<button
ref={ref}
className="w-full"
type="button"
onClick={() => setToggled(!isToggled)}
>
{/* 토글이 되면 ? 왼쪽이 활성화된 ui : 오른쪽이 활성화된 ui */}
{/* 스타일에만 isToggled 를 준다면 ? 콘솔에는 여전히 둘다 찍힐까? */}
{isToggled ?
(<div className="flex w-full items-center "><div className="w-1/2 shadow-lg bg-white rounded-lg "> 계좌 이체 </div><div className="w-1/2 "> 만나서 결제 </div></div>) :
(<div className="flex w-full">< div className="w-1/2" > 계좌 이체 </div><div className="w-1/2 shadow-lg bg-white rounded-lg">만나서 결제</div> </div>)}
{/* {isToggled ? "계좌 이체" : "만나서 결제"} */}
</button>
</div >
</>
);
};
export default forwardRef(PaymentToggleButton);
※ 댓글에 이슈 해결 완료 후 결과 또는 해결 과정 이미지 첨부
Context API 를 사용해서 파일 업로드 기능을 제작중입니다.
value 값을 기억해서 다른 페이지를 다녀와도 사진이 그대로 유지되어있도록 할 예정입니다.
현재 useState 를 이용하여 fileImages 데이터 안에 value를 넣어주었으나 value 값을 기억하지 않고 form 에 전송이 되지 않는 상황입니다. 업로드한 이미지를 useContext 로 CreateRoom 페이지로 데이터를 전달시켜서 Create 해야하는 상황인데 form 에 입력되지 않는 이유와 수정되야할 부분을 알수있을까요?
import { AppContext } from '@/App';
import imgUpload from '@/assets/icons/imgUpload.svg';
import { useContext, useEffect, useState } from 'react';
function FileUpload({ value, className, title, ...restProps }) {
const { updateCreateRoomForm } = useContext(AppContext);
const [fileImages, setFileImages] = useState(value);
useEffect(() => {
updateCreateRoomForm('uploadImage', fileImages);
}, [fileImages]);
const handleFileUpload = (e) => {
const { files } = e.target;
const fileImages = Array.from(files).map((file) => ({
image: URL.createObjectURL(file),
label: file.name,
}));
setFileImages(fileImages);
};
return (
<>
<div className=" mb-14">
<div className="flex justify-between items-center">
<p>사진 업로드</p>
<label htmlFor="photo" className={className}>
{title}
</label>
<input
type="file"
accept="image/jpg,image/png,image/jpeg,image/webp,image/avif"
name="photo"
id="photo"
onChange={handleFileUpload}
style={{ display: 'none' }}
{...restProps}
/>
</div>
<div className="flex justify-center border-2 my-4 rounded-lg border-dashed border-line-400">
{!fileImages && (
<img src={imgUpload} alt="photo" className="w-20 p-4" />
)}
{fileImages?.map((file) => {
return <img value={fileImages}
key={file.label} src={file.image} alt={file.label} />;
})}
</div>
</div>
</>
);
}
export default FileUpload;
import { AppContext } from '@/App';
import { useContext, useEffect, useState } from 'react';
function ContentTextarea(
{ value = "", title, placeholder, className, labelClassName, ...restProps }) {
const { updateCreateRoomForm } = useContext(AppContext);
const [data, setData] = useState(value);
useEffect(() => {
updateCreateRoomForm('content', data);
}, [data]);
const handleInputChange = (e) => {
setData(e.target.value);
};
return (
<>
<label htmlFor="content" className={labelClassName}>
내용
</label>
<textarea
value={data}
onChange={handleInputChange}
id="content"
placeholder={placeholder}
className={className}
{...restProps}
/>
</>
);
}
export default ContentTextarea;
※ 댓글에 이슈 해결 완료 후 결과 또는 해결 과정 이미지 첨부
파일 업로드 기능을 FileUpload.jsx 파일에 구현하여 아래와 같이 컴포넌트로 제작하였습니다.
파일업로드 컴포넌트로 업로드한 파일을 pocketHost 에 있는 form data 로 전송을 해야하는 상황입니다.
해당 양식에 맞게 파일을 첨부하여 pocketHost 에 전송에 성공하였으나
파일 미업로드시에는 아래와 같이 에러메세지가 뜹니다.
파일 미업로드시 에러메세지를 예방하는 법을 문의합니다.
import { useState, forwardRef } from "react";
import { imgUpload } from '../../assets/icons/svg-icons'
function FileUpload({ }, ref) {
const [fileImages, setFileImages] = useState(null);
const handleFileUpload = (e) => {
const { files } = e.target;
const fileImages = Array.from(files).map((file) => ({
image: URL.createObjectURL(file),
label: file.name,
}));
setFileImages(fileImages);
// img file const
console.log(photoRef.current.files);
const photoValue = photoRef.current.files;
if (!photoValue) {
// toast message here
return
}
};
return (<>
<div >
<label htmlFor="photo" className="sr-only">
사진 등록
</label>
<input
type="file"
accept="image/jpg,image/png,image/jpeg,image/webp,image/avif"
name="photo"
id="photo"
ref={ref}
onChange={handleFileUpload}
className=""
/>
<div className="" >
{!fileImages && (
<img
src={imgUpload}
alt="photo"
className="w-24"
/>
)}
{fileImages?.map((file) => {
return (
<img
key={file.label}
src={file.image}
alt={file.label}
className=""
/>
);
})}
</div>
</div>
</>)
}
export default forwardRef(FileUpload);
아래 코드를 적용시켜보았지만 똑같은 에러메세지가 나는 상황입니다. 파일 미업로드시 뜨는 에러를 어떻게 예방해야할까요?
} catch (error) {
if (!(error instanceof ClientResponseError)) {
console.error(error);
}}
※ 댓글에 이슈 해결 완료 후 결과 또는 해결 과정 이미지 첨부
🤔 문제 상황
🛠️ pocketHost를 활용한 공동구매 진행상태 렌더링 및 업데이트 기능
🛠️ pocketHost와 로컬스토리지 데이터 대조
없음
useContext 로 상태관리를 하면서 form 이 모두 담긴 페이지에서 한 카테고리 페이지를 클릭 후 돌아올때 다른 form 의 요소들이 그대로 저장되어 있도록 구현하려면 tan stack query를 사용해야할까요 ? 현재 Location 페이지에 다녀오면 입력했던 form의 선택값들이 reset 되는 상황입니다.
ContentTextarea.jsx
import { AppContext } from '@/App';
import { useEffect, useState, useContext, useId } from 'react';
function ContentTextarea(
{ title, placeholder, className, labelClassName, ...restProps }) {
const { id } = useId();
const { updateCreateRoomForm } = useContext(AppContext);
const [data, setData] = useState('');
useEffect(() => {
updateCreateRoomForm('content', data);
}, [data])
const handleInputChange = (e) => {
setData(e.target.value);
// console.log(setData);
};
return (
<>
<label htmlFor={id} className={labelClassName}>
{title}
</label>
<textarea
value={data}
onChange={handleInputChange}
id={id}
placeholder={placeholder}
className={className}
{...restProps}
/>
</>
);
}
export default ContentTextarea;
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';
import { useEffect } from 'react';
function CreateRoom() {
const { createRoomForm, updateCreateRoomForm } = useContext(AppContext);
const formRef = useRef(null);
const titleRef = 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 = createRoomForm.category;
const titleValue = titleRef.current.value;
const contentValue = createRoomForm.content;
console.log(contentValue);
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
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
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;
※ 댓글에 이슈 해결 완료 후 결과 또는 해결 과정 이미지 첨부
저희가 만든 “공구룸” 웹앱 서비스에서 알림 기능을 구현하려고 합니다.
포켓베이스 리얼타임 기능을 활용하면 푸시 알림 기능을 구현할수 있을것 같은데
푸시알림에 대한 자료를 검색하면 firebase 관련 자료밖에 나오지 않아서 알림 기능 구현 방법에 대해 문의합니다.
filter : ~ 상품이름 포함
[Kakao Developers](https://developers.kakao.com/docs/latest/ko/push/rest-api)
[React에서 Browser Notification (푸시 알림) 구현하기](https://mingule.tistory.com/68)
[Notifications API Standard](https://notifications.spec.whatwg.org/)
import React, { useState } from 'react';
function NotificationSender() {
const [message, setMessage] = useState('');
const sendNotification = async () => {
try {
const response = await fetch('YOUR_POCKETBASE_API_URL/send-notification', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_AUTH_TOKEN',
},
body: JSON.stringify({ message }),
});
if (response.ok) {
console.log('Notification sent successfully');
// 여기에서 필요한 처리를 수행하세요.
} else {
console.error('Error sending notification:', response.statusText);
}
} catch (error) {
console.error('Error sending notification:', error);
}
};
return (
<div>
<h2>Send Notification</h2>
<textarea
rows="3"
value={message}
onChange={(e) => setMessage(e.target.value)}
/>
<button onClick={sendNotification}>Send</button>
</div>
);
}
export default NotificationSender;
※ 댓글에 이슈 해결 완료 후 결과 또는 해결 과정 이미지 첨부
🛠️ 아이디는 이메일 형식, 비밀번호는 최소 8자 이상 입력하지 않은 시 안내 문구 적용
없음
Warning: Unexpected ref object provided for select. Use either a ref-setter function or React.createRef().
console 에 계속해서 다음과 같은 warning 메세지가 나타납니다. 문제가 나타나는 Status.jsx 파일안에서는 forwardRef를 사용중입니다. ref 코드에는 이상이 없어보입니다. 사용에는 이상이 없지만 해당 메세지가 안뜨게 하려면 어떻게 해야할까요?
import { string } from 'prop-types';
import { useId, forwardRef } from 'react';
function Status({ title, className, labelClassName, ...restProps }, ref) {
const { id } = useId();
return (
<>
<label htmlFor={id} className={labelClassName}>
{title}
</label>
<select
ref={ref}
id={id}
className={className}
{...restProps}
name="status"
defaultValue="대기중"
>
<option value="대기중">대기중</option>
<option value="진행중">진행중</option>
<option value="공구종료">공구종료</option>
</select>
</>
);
}
export default forwardRef(Status);
※ 댓글에 이슈 해결 완료 후 결과 또는 해결 과정 이미지 첨부
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.