이번 프로젝트에서 내가 담당한 부분은 바로 작성 페이지다.
사용자가 입력한 값을 지정한 형식에 맞춰 데이터베이스에 보내는 로직을 구현해야 했다.
레이아웃 이렇게 구성되어 있고, 재료 추가와 순서 추가의 경우 빈 객체가 들어있는 배열을 생성해서 state로 관리하고 버튼을 누르면 빈 객체가 추가되도록 만들어 두었다. 인풋 그룹은 map 메소드로 배열을 돌며 빈 배열 갯수만큼 출력된다.
테이블은
레시피 기본 정보를 저장하는 recipe_info,
재료 정보를 저장하는 recipe_ingredient,
레시피 순서를 저장하는 recipe
총 세개로 나누어져 있다.
테이블이 세개로 나누어져 있기 때문에 추후 상세페이지에서 레시피를 연결해서 불러오려면 세 데이터 모두 공통의 값을 갖고 있어야 했고, 나의 경우에는 recipe_info 테이블에 데이터를 보낸 뒤, 방금 보낸 데이터를 가져와서 해당 행의 id값을 재료와 순서에 추가했다.
그리고 재료와 순서는 인풋 개수가 고정된 게 아닌 추가하면 늘어나는 형식이라 어떻게 데이터를 입력해야하나 고민이 많이 됐는데 튜터님과 얘기하면서 대충 방향성이 잡혀서 얼른 코드를 짜보기 시작했다!
// 초기값
const initIngInfo = [
{
RECIPE_ID: 0, // recipe_info 연결을 위한 id 부분
ING_NAME: "",
ING_VOL: "",
},
{
RECIPE_ID: 0,
ING_NAME: "",
ING_VOL: "",
},
];
const initRecipeCont = [
{
RECIPE_ID: 0,
RECIPE_STEP: 0,
RECIPE_CONT: "",
},
];
// 인풋 추가할 때 사용하는 state
const [ingredientGroups, setIngredientGroups] = useState(initIngInfo);
const [recipeContGroups, setRecipeContGroups] = useState(initRecipeCont);
// 재료 그룹 추가하는 onClick 함수
const addIngGroup = () => {
setIngredientGroups([...ingredientGroups, {}]);
};
// 재료 그룹 삭제하는 onClick 함수
const removeIngGroup = (index) => {
if (ingredientGroups.length === 2) {
alert("최소 두 개의 재료는 추가되어야 합니다.");
return;
}
const newGroups = ingredientGroups.filter((_, idx) => idx !== index);
setIngredientGroups(newGroups);
};
// 레시피 순서 추가하는 onClick 함수
const addRecipeGroup = () => {
setRecipeContGroups([...recipeContGroups, {}]);
};
// 재료 그룹 삭제하는 onClick 함수
const removeRecipeGroup = (index) => {
if (recipeContGroups.length === 1) {
alert("최소 한 개의 레시피는 작성되어야 합니다.");
return;
}
const newGroups = recipeContGroups.filter((_, idx) => idx !== index);
setRecipeContGroups(newGroups);
};
버튼을 누르면 배열 안에 객체가 추가되거나 삭제되고 이 배열을 페이지 안에서 map을 통해 출력한다고 했다. 각 배열 안에 value 값을 저장하기 위해서는 지금 입력한 값이 어떤 값인지(ING_NAME인지 ING_VOL인지) 확인을 위한 type이 필요했고 배열 안의 몇번째 객체인지 알 수 있는 index가 필요했다.
첫번째 재료 칸에 당근 / 1개 라고 입력한다면
ingredientGroups[0] = {RECIPE_ID : id, ING_NAME : "당근", ING_VOL : "1개"}이런 모양이 되기를 원했다.
그렇게 작성한 코드
const ingInfoChange = (value, type, index) => {
ingredientGroups[index] = { ...ingredientGroups[index], [type]: value };
};
인자로 받아온 index를 통해 해당 객체에 접근해서 내부 값을 변경해준다. 그런데 입력할때마다 값이 초기화되면 안되니까 스프레드연산자로 기존 값을 유지하고 type에 해당하는 value만 변경되도록 했다.
레시피 순서 부분도 똑같이 구현했다.
recipe_info 테이블로 기본 정보 보내기
// 사용자 id 가져오기
const user = await supabase.auth.getUser();
const id = user.data.user.id;
// recipe_info 테이블에 레시피 정보 삽입
const { data } = await supabase
.from("recipe_info")
.insert([{ ...recipeInfo, USER_ID: id }])
.select();
const recipeId = data[0].RECIPE_ID;
재료와 순서 객체 안의 RECIPE_ID 값을 recipeId로 변경하기
// ingInfo 배열 안에서 모든 재료 객체 RECIPE_ID 변경
const updatedIngInfo = recipeContGroups.map((ingredient) => ({
...ingredient,
RECIPE_ID: recipeId,
}));
const updateRecipeCont = recipeContGroups.map((cont) => ({
...cont,
RECIPE_ID: recipeId,
}));
연결을 위해 각 그룹을 map으로 순회하며 모든 객체의 RECIPE_ID를 recipeId로 지정해주었다. 그리고 반환받은 update---가 최종적으로 데이터 베이스에 전송될 배열이다.
데이터 전송하고 state 초기화하기
await supabase.from("recipe_ingredient").insert(updatedIngInfo);
await supabase.from("recipe_flow").insert(updateRecipeCont);
// 상태 초기화
setRecipeInfo(initRecipeInfo);
setIngredientGroups(initIngInfo);
setRecipeContGroups(initRecipeCont);
완성된 데이터 보내고 상태 초기화 해주면 끝!
이제 빈칸이 있는 경우 alert을 띄워주거나 하는 유효성 검사만 마무리하면 된다...