Skip to content

Commit

Permalink
Fix: 리스트 생성 동일한 key사용 이슈 해결
Browse files Browse the repository at this point in the history
  • Loading branch information
seoyoung-min committed Feb 24, 2024
1 parent 93a9c70 commit 4ff9a9e
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 141 deletions.
10 changes: 10 additions & 0 deletions src/app/list/create/_components/item/ItemLayout.css.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { style } from '@vanilla-extract/css';
import { vars } from '@/styles/theme.css';
import * as fonts from '@/styles/font.css';

export const itemHeader = style({
width: '100%',
Expand All @@ -13,6 +14,15 @@ export const itemHeader = style({
overflow: 'hidden',
});

export const titleError = style([
fonts.bodySmall,
{
marginBottom: '4px',
flexShrink: '0',
color: vars.color.red,
},
]);

export const headerIcon = style({
flexShrink: '0',
});
Expand Down
67 changes: 36 additions & 31 deletions src/app/list/create/_components/item/ItemLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ interface ItemLayoutProps {
handleDeleteItem: () => void;
itemLength: number;
titleInput: ReactNode;
titleErrorMessage?: string;
commentTextArea: ReactNode;
commentLength: ReactNode;
linkModal: ReactNode;
Expand All @@ -29,6 +30,7 @@ export default function ItemLayout({
handleDeleteItem,
itemLength,
titleInput,
titleErrorMessage,
commentTextArea,
commentLength,
linkModal,
Expand All @@ -38,40 +40,43 @@ export default function ItemLayout({
handleImageAdd,
}: ItemLayoutProps) {
return (
<Accordion>
<AccordionSummary>
<div className={styles.itemHeader}>
<DndIcon width="18" height="18" alt="드래그앤드롭" className={styles.headerIcon} />
<div className={styles.rankAndTitle}>
<Label colorType={index === 0 ? 'blue' : 'skyblue'}>{`${index + 1}위`}</Label>
{titleInput}
</div>
</div>
</AccordionSummary>
<AccordionDetails className={styles.details}>
<div className={styles.moreInfo}>
{commentTextArea}
<div className={styles.countLength}>{commentLength}</div>
<div className={styles.toolbar}>
<div className={styles.fileButtons}>
{linkModal}
<ImageUploader index={index} handleImageAdd={handleImageAdd}>
{imageInput}
</ImageUploader>
<div>
{titleErrorMessage && <p className={styles.titleError}> {titleErrorMessage}</p>}
<Accordion>
<AccordionSummary>
<div className={styles.itemHeader}>
<DndIcon width="18" height="18" alt="드래그앤드롭" className={styles.headerIcon} />
<div className={styles.rankAndTitle}>
<Label colorType={index === 0 ? 'blue' : 'skyblue'}>{`${index + 1}위`}</Label>
{titleInput}
</div>
{itemLength > MIN_ITEM_COUNT && (
<button onClick={handleDeleteItem}>
<DeleteIcon fill={vars.color.gray9} alt="아이템 삭제" />
</button>
)}
</div>
</AccordionSummary>
<AccordionDetails className={styles.details}>
<div className={styles.moreInfo}>
{commentTextArea}
<div className={styles.countLength}>{commentLength}</div>
<div className={styles.toolbar}>
<div className={styles.fileButtons}>
{linkModal}
<ImageUploader index={index} handleImageAdd={handleImageAdd}>
{imageInput}
</ImageUploader>
</div>
{itemLength > MIN_ITEM_COUNT && (
<button onClick={handleDeleteItem}>
<DeleteIcon fill={vars.color.gray9} alt="아이템 삭제" />
</button>
)}
</div>

<div className={styles.previewContainer}>
{linkPreview}
{imagePreview}
<div className={styles.previewContainer}>
{linkPreview}
{imagePreview}
</div>
</div>
</div>
</AccordionDetails>
</Accordion>
</AccordionDetails>
</Accordion>
</div>
);
}
221 changes: 111 additions & 110 deletions src/app/list/create/_components/item/Items.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,119 +124,120 @@ export default function Items({ type, setItemChanged }: ItemsProps) {

const imageRegister = register(`items.${index}.imageUrl`);
return (
<div key={item.id}>
{titleError?.type !== 'required' && (
<p key={item.id} className={styles.error}>
{titleError?.message}
</p>
)}
<Draggable key={item.id} draggableId={item.id} index={index}>
{(provided, snapshot) => (
<div
className={snapshot.isDragging ? styles.draggingItem : styles.item}
key={item.id}
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
<ItemLayout
index={index}
handleDeleteItem={() => {
handleDeleteItem(index);
}}
itemLength={watchItems.length}
titleInput={
<input
className={titleError ? styles.errorTitle : styles.title}
placeholder={itemPlaceholder.title}
autoComplete="off"
maxLength={101}
{...register(`items.${index}.title`, itemTitleRules)}
readOnly={
type === 'edit' &&
listDetailData?.items.some((item) => item.id === getValues(`items.${index}.id`))
}
/>
}
commentTextArea={
<textarea
className={styles.comment}
placeholder={itemPlaceholder.comment}
rows={3}
maxLength={101}
{...register(`items.${index}.comment`, itemCommentRules)}
/>
}
commentLength={
<p className={commentError ? styles.errorCountLength : styles.countLength}>
{watchItems[index]?.comment?.length ?? 0}/100
</p>
}
linkModal={
<LinkModal
onCancelButtonClick={() => {
handleLinkModalCancel(index);
}}
onTriggerButtonClick={() => {
handleLinkModalOpen(index);
}}
onConfirmButtonClick={() => {
handleLinkModalConfirm(index);
<Draggable key={item.id} draggableId={item.id} index={index}>
{(provided, snapshot) => (
<div
className={snapshot.isDragging ? styles.draggingItem : styles.item}
key={item.id}
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
{/* {titleError?.type !== 'required' && (
<p key={item.id} className={styles.itemTitleError}>
{titleError?.message}
</p>
)} */}
<ItemLayout
index={index}
handleDeleteItem={() => {
handleDeleteItem(index);
}}
itemLength={watchItems.length}
titleErrorMessage={
titleError && titleError?.type !== 'required' ? titleError?.message : undefined
}
titleInput={
<input
className={titleError ? styles.errorTitle : styles.title}
placeholder={itemPlaceholder.title}
autoComplete="off"
maxLength={101}
{...register(`items.${index}.title`, itemTitleRules)}
readOnly={
type === 'edit' &&
listDetailData?.items.some((item) => item.id === getValues(`items.${index}.id`))
}
/>
}
commentTextArea={
<textarea
className={styles.comment}
placeholder={itemPlaceholder.comment}
rows={3}
maxLength={101}
{...register(`items.${index}.comment`, itemCommentRules)}
/>
}
commentLength={
<p className={commentError ? styles.errorCountLength : styles.countLength}>
{watchItems[index]?.comment?.length ?? 0}/100
</p>
}
linkModal={
<LinkModal
onCancelButtonClick={() => {
handleLinkModalCancel(index);
}}
onTriggerButtonClick={() => {
handleLinkModalOpen(index);
}}
onConfirmButtonClick={() => {
handleLinkModalConfirm(index);
}}
isLinkValid={!linkError && watchItems[index]?.link?.length !== 0}
>
<div className={styles.linkModalChildren}>
<input
className={styles.linkInput}
type="url"
placeholder={itemPlaceholder.link}
autoComplete="off"
{...register(`items.${index}.link`, itemLinkRules)}
/>
{watchItems[index]?.link?.length !== 0 && linkError && (
<p className={styles.error}>{linkError.message}</p>
)}
</div>
</LinkModal>
}
imageInput={
<input
className={styles.imageInput}
type="file"
accept=".jpg, .jpeg, .png"
id={`${index}-image`}
{...imageRegister}
onChange={(e) => {
handleImageChange(e, imageRegister);
}}
/>
}
linkPreview={
watchItems[index]?.link && (
<LinkPreview
url={watchItems[index].link}
handleClearButtonClick={() => {
setValue(`items.${index}.link`, '');
}}
isLinkValid={!linkError && watchItems[index]?.link?.length !== 0}
>
<div className={styles.linkModalChildren}>
<input
className={styles.linkInput}
type="url"
placeholder={itemPlaceholder.link}
autoComplete="off"
{...register(`items.${index}.link`, itemLinkRules)}
/>
{watchItems[index]?.link?.length !== 0 && linkError && (
<p className={styles.error}>{linkError.message}</p>
)}
</div>
</LinkModal>
}
imageInput={
<input
className={styles.imageInput}
type="file"
accept=".jpg, .jpeg, .png"
id={`${index}-image`}
{...imageRegister}
onChange={(e) => {
handleImageChange(e, imageRegister);
/>
)
}
imagePreview={
watchItems[index]?.imageUrl !== '' && (
<ImagePreview
image={watchItems[index]?.imageUrl}
handleClearButtonClick={() => {
setValue(`items.${index}.imageUrl`, '');
}}
/>
}
linkPreview={
watchItems[index]?.link && (
<LinkPreview
url={watchItems[index].link}
handleClearButtonClick={() => {
setValue(`items.${index}.link`, '');
}}
/>
)
}
imagePreview={
watchItems[index]?.imageUrl !== '' && (
<ImagePreview
image={watchItems[index]?.imageUrl}
handleClearButtonClick={() => {
setValue(`items.${index}.imageUrl`, '');
}}
/>
)
}
handleImageAdd={setItemChanged}
/>
</div>
)}
</Draggable>
</div>
)
}
handleImageAdd={setItemChanged}
/>
</div>
)}
</Draggable>
);
})}
{provided.placeholder}
Expand Down

0 comments on commit 4ff9a9e

Please sign in to comment.