
프로젝트를 진행하면서, 임무 분담을 하던 중 내 역할은 롤링페이지를 생성하는 페이지를 제작해야 했다.
- 토글 버튼의 선택에 따라 배경화면을 컬러, 이미지 선택지로 보여준다.
- 받는 사람의 이름, 배경화면(이미지면 주소, 또는 컬러)를 받고, 생성하기 버튼을 누르면 해당 데이터를 POST한다. 즉, 롤링페이지를 생성한다.
- 생성에 성공하면 /post/{id} 페이지로 이동한다.
- 이름을 입력하지 않으면 버튼은 disabled 상태가 된다.

기존 팀원들이 만든 컴포넌트를 가져오다 보니, 요구하는 디자인에 맞지 않는 문제가 있었다.
또한 옵션 선택 버튼은 선택한 요소만 체크 표시가 되어있어야 하는데 전부 체크가 되어있는, 미완성의 상태였다.

우선 페이지가 어떻게 동작할 지 구조를 설계했다.
const [postData, setPostData] = useState({
team: '2-12',
name: '',
backgroundColor: 'beige',
backgroundImageURL: null,
});
state를 활용하여 초기 post 데이터를 설정했다.
const InputStyle = styled.input`
display: flex;
width: 100%;
우선 input width를 수정하였다.
<Input
placeholder="받는 사람 이름을 입력해주세요."
onChange={setPostData}
postData={postData}
/>
input 컴포넌트에는, placeholder를 지정하고, 변할 때 postData를 바꾸도록 props로 전달해주었다.
// Input.jsx
const handleChange = (e) => {
onChange({ ...postData, name: e.target.value });
};
// CreateRecipientPage
const handleLoad = async () => {
let result;
try {
result = await getBackgroundImages();
setBackgroundImages([...result]);
} catch (error) {
return;
}
};
useEffect(() => {
handleLoad();
}, []);
페이지 최초 로딩 시 이미지 데이터를 가져오도록 했다. 이미지에 대한 내용은 backgroundImages라는 state에 저장하도록 했다.
<ToggleBtn onClick={setIsActive} isActive={isActive} />
// ToggleBtn.jsx
const handleClick = () => {
onClick(!isActive);
};
토글 버튼을 클릭할 때 마다 isActive를 참, 거짓으로 설정 했다.
// CreateRecipientsPage
<Selector
isImage={isActive}
onClick={setPostData}
postData={postData}
backgroundImages={backgroundImages}
/>
페이지 컴포넌트에서는 isActive에 따른 이미지 배경 분기를 props로 전달했다.
클릭 이벤트 onClick에 사용하기 위해 postData를 수정하도록 props로 주었고, 로딩될 때 받아온 이미지 데이터를 props로 전달해줬다.
const COLOR = [
{ value: 'beige', color: 'var(--orange-200, #FFE2AD)' },
{ value: 'purple', color: 'var(--purple-200, #ECD9FF)' },
{ value: 'blue', color: 'var(--blue-200, #B1E4FF)' },
{ value: 'green', color: 'var(--green-200, #d0f5c3)' },
];
const OptionItem = styled.button`
background: ${({ $background }) => ($background ? $background : 'red')};
`;
const CheckIcon = styled.img`
display: flex;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
padding: 1rem;
align-items: flex-start;
border-radius: 10rem;
background: var(--gray-500, #555);
`;
export default function Option({
isImage,
onClick,
postData,
backgroundImages,
}) {
const [selectedIndex, setSelectedIndex] = useState(0);
const BACKGROUND = backgroundImages;
const data = isImage ? BACKGROUND : COLOR;
return (
<OptionContainer>
{data.map((item, index) => {
return (
<OptionItem
key={index}
$background={isImage ? `url(${item})` : item.color}
id={index}
type="button"
onClick={handleClick}>
{selectedIndex === index && (
<CheckIcon src={checkImg} alt="체크됨" />
)}
</OptionItem>
);
})}
</OptionContainer>
);
}
미완성인 부분이 많아 사실상 스타일을 제외하고 처음부터 제작해야 했다.
props로 전달받은 backgroundImages를 저장하고, isImage 분기에 따라 map에 사용할 데이터를 바꿔줬다.
map에서는 데이터를 반복하여 선택 박스를 생성했고, OptionItem에 스타일 분기를 위한 $background 및 id값, onClick 이벤트, 체크에 따른 CheckIcon을 반환하도록 했다.
const handleClick = (e) => {
const id = Number(e.target.id);
if (isImage) {
onClick({ ...postData, backgroundImageURL: BACKGROUND[id] });
setSelectedIndex(Number(id));
} else {
onClick({
...postData,
backgroundColor: COLOR[id].value,
backgroundImageURL: null,
});
setSelectedIndex(Number(id));
}
};
OptionItem의 id를 사용해 데이터를 바꿔주도록 했다.
destructuring 문법을 활용해 페이지 컴포넌트의 postData를 수정하도록 했다.
이미지 분기에 따라 backgroundImageURL을 추가하거나, backgroundColor를 바꿔주도록 했다.
선택한 인덱스를 id값으로 반영하도록 해서 체크 표시를 할 수 있도록 state로 지정해줬다.
useEffect(() => {
setSelectedIndex(0);
if (isImage) {
onClick({ ...postData, backgroundImageURL: BACKGROUND[0] });
} else {
onClick({
...postData,
backgroundColor: COLOR[0].value,
backgroundImageURL: null,
});
}
}, [isImage]);
토글버튼에 선택에 따른 isImage값이 바뀌면, 즉 이미지에서 컬러 또는 컬러에서 이미지로 수정될 때 처음 값을 지정해주기 위해 선택 id를 0으로 초기화 시켜줬다.
// CreateRecipientPage.jsx
const handleSubmit = async (e) => {
e.preventDefault();
let result;
try {
result = await postRecipients(postData);
} catch (error) {
return;
}
console.log(result);
};
다시 페이지로 돌아와서 제출버튼 클릭 시 저장된 포스트 할 데이터를 POST 요청 시켜줬다.
이후 콘솔로 출력해서 잘 보내졌는지 확인할 수 있도록 하였다.



post 요청이 성공하여 제대로 결과값을 받아왔다.
- 생성하기 버튼 클릭 후 생성에 성공하면 /post/{id} 페이지로 이동한다.
- 이름을 입력하지 않으면 버튼은 disabled 상태가 된다.
- 시안에서 제시된 페이지 디자인에 맞게 요소들의 간격을 배치 해야한다.