React/React_Project
[React] 팀원 구성 박스 구현
cheon seung hyeon
2023. 9. 18. 13:21
기능 요약
1. 입력 내용을 State의 배열 형태로 저장
2. 포지션 추가 시 팀원 구성 박스 생성
3. 해당 포지션 클릭 시 이름과 역할 추가 생성
3-1. 이름과 역할이 작성되지 않았다면 생성되지 않음
4. 생성된 박스과 이름,역할을 삭제
1. State 배열 형태로 저장
const [teamInfoBoxes, setTeamInfoBoxes] = useState([
{ id: 1, addInfo: false, infoData: [{ id: 1, name: '', position: '', role: '' }] },
]);
팀원 박스와 이름 역활 데이터를 각각 저장하는 2차원 배열 형태로 저장
id : 팀원 박스
addInfo : 팀원 박스를 추가하는 트리거
infoData : 팀원 박스 내 입력한 정보
2. 추가 팀원 박스 생성
const handleInfoChange = (newData: TeamMember[], boxId: number) => {
setTeamInfoBoxes((prevBoxes) => {
const updatedBoxes = prevBoxes.map((box) => {
if (box.id === boxId) {
return { ...box, infoData: newData };
}
return box;
});
return updatedBoxes;
});
};
const handleAddInfoBox = () => {
const newId = teamInfoBoxes.length + 1;
const newInfoBox = {
id: newId,
addInfo: false,
infoData: [{ id: 1, name: '', position: '', role: '' }],
};
setTeamInfoBoxes([...teamInfoBoxes, newInfoBox]);
};
현재 팀원 박스의 길이 + 1 의 id를 가진 빈 팀원 박스를 생성
생성된 팀원 박스를 현 State에 업데이트 시킴으로 새로운 팀원 박스를 생성
const handleDeleteInfoBox = (boxId: number) => {
setTeamInfoBoxes((prevBoxes) => prevBoxes.filter((box) => box.id !== boxId));
};
filter 함수와 id를 이용해서 삭제 State 삭제 함수 생성
export const useTeamInfoBoxes = () => {
const [teamInfoBoxes, setTeamInfoBoxes] = useState([
{ id: 1, addInfo: false, infoData: [{ id: 1, name: '', position: '', role: '' }] },
]);
const handleInfoChange = (newData: TeamMember[], boxId: number) => {
setTeamInfoBoxes((prevBoxes) => {
const updatedBoxes = prevBoxes.map((box) => {
if (box.id === boxId) {
return { ...box, infoData: newData };
}
return box;
});
return updatedBoxes;
});
};
const handleAddInfoBox = () => {
const newId = teamInfoBoxes.length + 1;
const newInfoBox = {
id: newId,
addInfo: false,
infoData: [{ id: 1, name: '', position: '', role: '' }],
};
setTeamInfoBoxes([...teamInfoBoxes, newInfoBox]);
};
const handleDeleteInfoBox = (boxId: number) => {
setTeamInfoBoxes((prevBoxes) => prevBoxes.filter((box) => box.id !== boxId));
};
return {
teamInfoBoxes,
handleInfoChange,
handleAddInfoBox,
handleDeleteInfoBox,
};
};
해당 함수들을 커스텀 훅으로 만들어 외부 컴포넌트에서 사용하기 쉽게 생성
const { teamInfoBoxes, handleInfoChange, handleAddInfoBox, handleDeleteInfoBox } =
useTeamInfoBoxes();
위와 같이 컴포넌트에서 호출해서 사용
하지만 컴포넌트 1개에 이 함수들을 이용해서 박스 모델을 만들기에는 코드가 너무 길어지기 때문에
TeamInfoInputBox라는 컴포넌트를 만들어서 연결하는 식으로 동작
<FlexWrapContainer>
{teamInfoBoxes.map((box) => (
<TeamInfoInputBox
key={box.id}
addInfo={box.addInfo}
onRemove={() => handleDeleteInfoBox(box.id)}
onInfoChange={(newData) => handleInfoChange(newData, box.id)}
infoData={box.infoData}
/>
))}
<TeamInfoInputBox
onClick={handleAddInfoBox}
addInfo
infoData={[{ id: 1, name: '', position: '', role: '' }]}
/>
</FlexWrapContainer>
TeamInfoInputBox의 경우
export const TeamInfoInputBox = ({
addInfo,
onClick,
onRemove,
onInfoChange,
infoData,
}: {
addInfo?: boolean;
onClick?: () => void;
onRemove?: () => void;
onInfoChange?: (newData: TeamMember[]) => void;
infoData: TeamMember[];
}) => {
const [position, setPosition] = useState('');
const handleAddInfo = () => {
// 이름과 역할 모두 작성되지 않았을 때
if (infoData.some((member) => member.name === '' || member.role === '')) {
alert('이름과 역할을 모두 작성해주세요.');
} else {
const newMember: TeamMember = { id: infoData.length + 1, name: '', position: '', role: '' };
if (onInfoChange) {
onInfoChange([...infoData, newMember]);
}
}
};
const handleDeleteInfo = (index: number) => {
const updatedData = [...infoData];
updatedData.splice(index, 1);
if (onInfoChange) {
onInfoChange(updatedData);
}
};
return (
<div
onClick={onClick}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
onClick?.();
}
}}
role="button"
tabIndex={0}
>
<InfoContainer>
<InfoUpperContainer>
<RowContainer gap="3.2rem">
<InfoInput2
placeholder="역할을 선택해주세요"
large
value={position}
onChange={(e) => {
setPosition(e.target.value);
}}
color={`${theme.palette.gray.white}`}
/>
<button type="button" onClick={onRemove}>
<img src="/img/close.png" alt="Close Icon" />
</button>
</RowContainer>
</InfoUpperContainer>
<InfoDownContainer>
<Section gap="0.8">
{infoData.map((item, index) => (
<Section key={item.id} gap="0.8">
<RowContainer gap="2.4rem">
<InfoInput2
placeholder="이름을 입력해주세요"
large
value={item.name}
onChange={(e) => {
const updatedData = [...infoData];
updatedData[index].name = e.target.value;
updatedData[index].position = position;
if (onInfoChange) {
onInfoChange(updatedData);
}
}}
/>
<button type="button" onClick={() => handleDeleteInfo(index)}>
<img src="/img/close.png" alt="Close Icon" />
</button>
</RowContainer>
<InfoInput2
placeholder="어떤 역할을 했나요?"
value={item.role}
onChange={(e) => {
const updatedData = [...infoData];
updatedData[index].role = e.target.value;
if (onInfoChange) {
onInfoChange(updatedData);
}
}}
/>
</Section>
))}
{!addInfo && (
<Body5
onClick={handleAddInfo}
style={css`
${theme.typography.body2};
color: ${theme.palette.primary[500]};
text-decoration: ${theme.palette.primary[500]} 0.25rem solid underline;
text-underline-offset: 0.5rem;
:hover {
cursor: pointer;
}
`}
>
해당 포지션에 팀원을 더 추가하고 싶어요
</Body5>
)}
</Section>
</InfoDownContainer>
<Overlay addInfo={addInfo} />
</InfoContainer>
</div>
);
};
TeamInfoInputBox.defaultProps = { addInfo: false };
해당 컴포넌트에서 이름, 역할을 추가하는 함수들과 삭제하는 함수 정의
const handleAddInfo = () => {
// 이름과 역할 모두 작성되지 않았을 때
if (infoData.some((member) => member.name === '' || member.role === '')) {
alert('이름과 역할을 모두 작성해주세요.');
} else {
const newMember: TeamMember = { id: infoData.length + 1, name: '', position: '', role: '' };
if (onInfoChange) {
onInfoChange([...infoData, newMember]);
}
}
};
const handleDeleteInfo = (index: number) => {
const updatedData = [...infoData];
updatedData.splice(index, 1);
if (onInfoChange) {
onInfoChange(updatedData);
}
};
다른 점은 위의 함수에선 teaminfoboxs 에 팀원 박스 자체를 추가하기 때문에 id가 추가
Teaminfoinputbox에선 팀원 박스 내에 있는 infoData 속 id가 추가