(모달) 수정화면을 위해 모달창이 켜지고 꺼지는 것이 오류 발생
▶ modal 창 여는 버튼과 닫는 버튼에 각각 true, false 표시
(객체, 배열)타입을 3가지로 나눠 화면에 표시, 수정과 삭제에 문제 생김
▶ 수정, 삭제 관련해 배열을 따로 뒀으나, index 사용을 위해 easy, middle, advance로 타입 나눈 것을 한 객체(wordLists)로 통일
(useEffect) 관련 내용 정리
객체 : 이름과 값으로 구성된 프로퍼티의 정렬되지 않은 값
프로퍼티가 함수로 올 경우 메소드라고 함
배열 : map으로 이루어진, 순서가 있는 집합
▶아래의 예시는 배열 안에 객체를 넣은 것
wordLists: [
{
id: 2,
english: "red",
korean: "빨강",
type: "easy",
},
{
id: 4,
english: "blue",
korean: "파랑",
type: "easy",
},
{
id: 6,
english: "yellow",
korean: "노랑",
type: "middle",
},
]
splice : 배열.splice(삭제하려고 하는 위치, 삭제하려는 수, 넣을 값)
▶ 기존 배열에 영향 있음
// splice 예시
var test = [
{id: 1, name: 'a'},
{id: 2, name: 'b'},
{id: 3, name: 'c'},
{id: 4, name: 'd'},
]
test.splice(2, 1, {id: 3, name: "test"}) // {id: 3, name: 'c'}
console.log(test)
// {id: 1, name: 'a'}
// {id: 2, name: 'b'}
// {id: 3, name: 'test'}
// {id: 4, name: 'd'}
filter : 요소를 걸러내어 배열로 true/false 반환, 없으면 빈 배열, 조건을 줄 수 있음
▶ 기존 배열에 영향 없음
// filter 예시
var test = [
{id: 1, name: 'a'},
{id: 2, name: 'b'},
{id: 3, name: 'c'},
{id: 4, name: 'd'},
]
test.filter((t) => t.id > 1)
//{id: 2, name: 'b'},
//{id: 3, name: 'c'},
//{id: 4, name: 'd'},
console.log(test)
// {id: 1, name: 'a'}
// {id: 2, name: 'b'}
// {id: 3, name: 'test'}
// {id: 4, name: 'd'}
map : 요소를 일괄적으로 변경
▶ 기존 배열에 영향 없음
//map 예시
var test = [
{id: 1, name: 'a'},
{id: 2, name: 'b'},
{id: 3, name: 'c'},
{id: 4, name: 'd'},
]
test.map((t, i) => t.id) //t는 test 안의 객체, i는 index
//[1, 2, 3, 4]
test.map((t, i) => t.name)
//['a', 'b', 'c', 'd']
console.log(test)
// {id: 1, name: 'a'}
// {id: 2, name: 'b'}
// {id: 3, name: 'test'}
// {id: 4, name: 'd'}
▶ 수정 버튼 누름
▶ 수정 하기 전 단어 화면에 나옴
▶ 영어, 한글, 타입(easy -> middle)로 변경
▶ 수정 버튼 누르면 easy에서 middle로 변경됨
▶ 수정한 값을 삭제 버튼을 누름
▶ 취소 누르면 다시 원래 화면으로 돌아옴
▶ 삭제 버튼 누르면 삭제됨
▶ 화면에서 삭제됨(실제 데이터도 삭제됨)
[WordList.js]
const WordList = () => {
const [modal, setModal] = useState(false); //수정 모달
const [removeModal, setRemoveModal] = useState(false); //삭제 모달
const onReviseWord = (e) => {
setModal(true);
};
const onRemoveWord = (e) => {
setRemoveModal(true);
};
return (
<>
{/* 수정 모달창 : true일 때 열리고 false일 때 안 보임 */}
{modal ? <ReviseWordModal isId={id} setModal={setModal} /> : null}
{/* 삭제 모달창 : true일 때 열리고 false일 때 안 보임 */}
{removeModal ? (
<RemoveWordModal isId={id} setRemoveModal={setRemoveModal} />
) : null}
</>
)
export default wordList;
[ReviseWordModal.js]
▶ 삭제도 동일함
▶ 모달과 관련된 css는 tailwindcss 적용
import { Fragment, useRef, useState } from "react";
import { Dialog, Transition, Listbox } from "@headlessui/react";
import { useSelector, useDispatch } from "react-redux";
import {
ArrowsRightLeftIcon,
ArrowDownIcon,
CheckIcon,
} from "@heroicons/react/24/outline";
const ReviseWordModal = ({ isId, setModal }) => { //보내온 데이터 받음
const [open, setOpen] = useState(true);
const [selected, setSelected] = useState(typesName[0]);
const onReviseWordSubmit = () => {
setModal(false); //true -> false로 바꿀 것
};
const onOpenCloseModal = () => {
console.log("open", open);
setModal(false); //true -> false로 바꿀 것
};
const cancelButtonRef = useRef(null);
return (
<Transition.Root show={open} as={Fragment}>
<Dialog
as="div"
className="relative z-10"
initialFocus={cancelButtonRef}
onClose={setOpen}
>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-white text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg">
<div className="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div className="sm:flex sm:items-start">
<div className="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-light-green sm:mx-0 sm:h-10 sm:w-10">
<ArrowsRightLeftIcon
className="h-6 w-6 text-white"
aria-hidden="true"
/>
</div>
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<Dialog.Title
as="h3"
className="text-lg font-medium leading-6 text-gray-900"
>
단어 수정
</Dialog.Title>
<div className="flex w-96">
<div>
<input
onChange={onChangeEnglish}
placeholder={wordLists[isId].english}
// placeholder="englsih"
type="text"
name="english"
className="sm:600 w-52 grid grid-cols-2 gap-4 place-content-center
pl-2 h-9 placeholder:italic placeholder:text-slate-400 flex items-start bg-white border-solid border-2 border-light-green group-hover:opacity-80 rounded-full m-2"
/>
<input
onChange={onChangeKorean}
placeholder={wordLists[isId].korean}
// placeholder="korean"
type="text"
name="korean"
className="sm:600 w-52 grid grid-cols-2 gap-4 place-content-center
pl-2 h-9 placeholder:italic placeholder:text-slate-400 flex items-start bg-white border-solid border-2 border-light-green group-hover:opacity-80 rounded-full m-2"
/>
</div>
<div className="ml-10 mt-2">
<Listbox value={selected} onChange={setSelected}>
<div className="relative ">
<Listbox.Button className="relative w-full cursor-default rounded-lg bg-white py-2 pl-2 pr-10 text-left shadow-md focus:outline-none focus-visible:border-indigo-500 focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 focus-visible:ring-offset-2 focus-visible:ring-offset-orange-300 sm:text-sm border-2 border-light-green">
<span className="block">{selected.name}</span>
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
<ArrowDownIcon
className="h-5 w-5 text-gray-400"
aria-hidden="true"
/>
</span>
</Listbox.Button>
<Transition
as={Fragment}
leave="transition ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Listbox.Options className="absolute mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
{typesName.map((t, typeIdx) => (
<Listbox.Option
key={typeIdx + 1}
className={({ active }) =>
`relative cursor-default select-none py-2 pl-2 pr-4 ${
active
? "bg-light-orange rounded-lg text-black"
: "text-gray-900"
}`
}
value={t}
>
{({ selected }) => (
<>
<span
className={`block ${
selected
? "font-medium"
: "font-normal"
}`}
name={t.name}
>
{t.name}
</span>
{selected ? (
<span className="absolute inset-y-0 left-0 flex items-center pl-3 text-amber-600">
<CheckIcon
className="h-5 w-5 ml-11"
aria-hidden="false"
/>
</span>
) : null}
</>
)}
</Listbox.Option>
))}
</Listbox.Options>
</Transition>
</div>
</Listbox>
</div>
</div>
</div>
</div>
</div>
<div className="bg-gray-50 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6">
<button
type="button"
className="inline-flex w-full justify-center rounded-md border border-transparent bg-light-green px-4 py-2 text-base font-medium text-white shadow-sm hover:bg-dark-green focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 sm:ml-3 sm:w-auto sm:text-sm"
onClick={onReviseWordSubmit}
>
수정
</button>
<button
type="button"
className="mt-3 inline-flex w-full justify-center rounded-md border border-gray-300 bg-white px-4 py-2 text-base font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"
onClick={onOpenCloseModal}
ref={cancelButtonRef}
>
취소
</button>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
);
};
export default ReviseWordModal;
▶ 타입 따라 결과창을 나둠(easy, middle, advance)
▶ wordLists와 easyLists, middleLists, advanceLists 만듦
▶ 문제점 : 데이터 수정, 삭제 시 index값 필요할 때 문제 생김
[wordLists 데이터]
wordLists: [
{
id: 2,
english: "red",
korean: "빨강",
type: "easy",
},
{
id: 4,
english: "blue",
korean: "파랑",
type: "easy",
},
{
id: 6,
english: "yellow",
korean: "노랑",
type: "middle",
},
{
id: 8,
english: "green",
korean: "초록",
type: "advance",
},
]
[wordLists와 easyLists, middleLists, advanceLists]
▶ filter 적용했었음
▶ wordLists와 나머지 filter가 적용된 것들의 인덱스가 다름
▶ 수정, 삭제 시 array.splice 이용(https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/splice)
splice() 메서드 : 배열의 기존 요소 삭제나 교체 할 때 사용(인덱스 값 이용)
import { useSelector } from "react-redux";
//redux-toolkit으로 만든 wordSlice에서 가져옴
const { wordLists } = useSelector((state) => state.word); //id: 2, 4, 6, 8(4개, 인덱스 0, 1, 2, 3)
const easyLists = wordLists.filter((word) => word.type ==='easy') //id: 2, 4(2개, 인덱스 0, 1)
const middleLists = wordLists.filter((word) => word.type ==='middle') //id: 6(1개, 인덱스 0)
const advanceLists = wordLists.filter((word) => word.type ==='advance') //id: 8(1개, 인덱스 0)
{wordLists.map((word, index) => {
if (word.type === "easy") {
return (
<>
...
</>
);
}
})}
▶ 참고 : https://www.youtube.com/watch?v=pvTuVXlrGUY
▶ 컴포넌트가 리렌더링 될 때마다 리액트에게 어떤 일을 실행시켜달라 요청 가능
▶ 특정 state가 변경될 때만 실행되게 하고 싶음([]에 특정 state를 넣기)
▶ 첫 렌더링 이후에 이후에는 실행시키고 싶지 않을 때
두 번째 array([])를 빈칸으로 남겨두면 됨
import React, {useEffect} from "react"
useEffect(() => {
}, [])
.