전체 선택 / 해제 눌렀을 때 개별 항목들이 제대로 동작하지 않음
▶ checked 여부 확인 후 개별항목과 전체 항목이 연결되게 하기
체크박스 클릭 시 status="C", 체크박스 해제 시 status="A"
▶ saga로 넘길 때 "C"인지 "A"인지 조건문
일부를 미리 체크하고 전체 체크 / 해제를 누를 경우 누적되서 적용됨
▶ 배열을 초기화 하는 것이 핵심(12.07 수정)
▶ 길이를 담당하는 배열 : checkedWordList
▶ 개별 항목에서 2개 정도 누르고 전체 체크 눌렀을 때 전체 항목이 20개라면 20개가 다 체크가 됬다는 게 아닌 22개가 체크가 됬다고 나옴
▶ 개별 항목에 있는 값이 누적되어 전체 체크에 영향을 줌
▶ 초기화가 필요했음(wordSlice 수정함)
배열 초기화
//1
const array = [1, 2, 3]
array.length = 0;
console.log(array) // []
//2
let array2 = [1, 2, 3]
array2 = []
console.log(array2) // []
https://myhappyman.tistory.com/115
https://developer.mozilla.org/en-US/docs/Web/API/Element/classList
https://hianna.tistory.com/453
https://nscworld.net/2021/06/16/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EB%B0%B0%EC%97%B4-%EC%82%AD%EC%A0%9C%ED%95%98%EB%8A%94-%EC%97%AC%EB%9F%AC%EA%B0%80%EC%A7%80-%EB%B0%A9%EB%B2%95/
const obj = [
{id: 123, name: "A"},
{id: 123, name: "B"},
{id: 123, name: "C"},
{id: 123, name: "D"},
]
//개별 항목 변경
console.log(obj[0]) //{id: 123, name: 'A'}
obj[0].id = 1
console.log(obj[0]) //{id: 1, name: 'A'}
const obj2 = [
{id: 123, name: "A"},
{id: 123, name: "B"},
{id: 123, name: "C"},
{id: 123, name: "D"},
]
//전체 항목 변경
obj2.map((o, i) => {
o.id = 1
})
console.log(obj2)
//{id: 1, name: 'A'}
//{id: 1, name: 'B'}
//{id: 1, name: 'C'}
//{id: 1, name: 'D'}
1) 전체 선택
2) 개별 선택 후 해제
3) 개별 선택
(WordList.js)
(feature/wordSlice.js)
(sagas/wordSaga.js)
import React, { useCallback, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import {
changeStatusWordRequest,
changeStatusWordAllRequest,
} from "../../redux/feature/wordSlice";
const WordList = () => {
const dispatch = useDispatch();
const checkedWordList = [];
const { wordLists } = useSelector((state) => state.word);
wordLists.map((word, i) => {
if (word.status === "C") {
checkedWordList.push(word);
}
});
//전체 선택
const onClickAllSelected = useCallback((e) => {
const checkboxClickedAll = e.target;
checkboxClickedAll.classList.toggle(status);
const checkboxes = document.querySelectorAll("input[name=checkItem]");
if (checkboxClickedAll.checked) {
dispatch(changeStatusWordAllRequest({ status: status }));
for (let i = 0; i < checkboxes.length; i++) {
checkboxes[i].checked = checkboxClickedAll.checked;
}
} else {
dispatch(changeStatusWordAllRequest({ status: "A" }));
for (let i = 0; i < checkboxes.length; i++) {
checkboxes[i].checked = checkboxClickedAll.checked;
}
}
}, []);
//개별 선택
const onClickSelected = useCallback((e) => {
const checkboxClicked = e.target;
const wordIndex = e.target.value;
checkboxClicked.classList.toggle(status);
if (checkboxClicked.checked) {
dispatch(changeStatusWordRequest({ id: wordIndex, status: status }));
} else if (!checkboxClicked.checked) {
dispatch(changeStatusWordRequest({ id: wordIndex, status: "A" }));
}
}, []);
return (
<>
{/* checkbox */}
<div className="absolute top-64 right-0 md:right-20 lg:right-52">
<div className="flex items-center mb-2">
<input
onChange={onClickAllSelected}
id="checkboxAll"
type="checkbox"
className="w-4 h-4 text-light-green bg-gray-900 rounded border-light-green focus:ring-light-green dark:focus:ring-light-green dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"
/>
<label
htmlFor="checkboxAll"
className="ml-2 text-sm font-bold text-gray-900 border-light-green"
>
전체 선택 / 해제
</label>
</div>
<p>
체크된 단어 개수 : {checkedWordList.length}/{wordLists.length}
</p>
</div>
<div className="lg:w-full relative">
<div className="h-max mx-20 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:gap-x-1">
{/* Easy start */}
<div className="group relative rounded-lg p-3 lg:w-80 lg:ml-10">
<div className="overflow-y-auto max-h-96 aspect-w-1 aspect-h-1 w-full overflow-hidden rounded-md bg-white border-2 border-light-green lg:aspect-none">
<div>
<h1 className="text-slate-900 font-medium px-3 pt-2">
🥉 Easy
</h1>
</div>
{/* <div className="flex absolute left-32 top-4">
<button className="hover:bg-gray-100 w-8 h-8 rounded-lg">
<ChevronLeftIcon onClick={onClickLeftEasy} />
</button>
<div className="font-medium mt-1">{page}</div>
<button className="hover:bg-gray-100 w-8 h-8 rounded-lg">
<ChevronRightIcon onClick={onClickRightEasy} />
{minIndex} {maxIndex}
</button>
</div> */}
{/* item start */}
{wordLists.map((word, index) => {
if (
word.type === "easy"
return (
<>
<div
key={word}
className="flex items-start bg-gray-400 group-hover:opacity-80 rounded-lg m-2"
>
<div className="h-24 w-90 sm:600 w-96 lg:w-48">
<div className="flex py-5 pl-1">
<input
onClick={onClickSelected}
value={index}
id="checkItem"
name="checkItem"
type="checkbox"
className="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500"
/>
</div>
<li className="flex first:pt-0 last:pb-0">
<div className="relative bottom-10 ml-9 overflow-hidden">
<p className="text-sm font-medium text-slate-900">
{word.english} ({index})
</p>
<p className="text-sm text-slate-900 truncate">
{word.korean} {word.id} {word.status}
</p>
</div>
</li>
</div>
<div className="relative h-24 w-24 py-2">
<Menu
as="div"
className="relative inline-block text-left"
>
<div>
<Menu.Button className="inline-flex justify-center rounded-md border border-gray-300 bg-white px-1 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 focus:ring-offset-gray-100">
Options
<ChevronDownIcon
className="ml-1 h-5 w-5"
aria-hidden="true"
/>
</Menu.Button>
</div>
<Transition
as={Fragment}
enter="transition ease-out duration-90"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items className="absolute right-0 z-10 mt-2 w-24 origin-top-right rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
<div className="py-0">
<Menu.Item>
{({ active }) => (
<button
value={index}
onClick={onReviseWord}
className={
(active
? "rounded-md bg-gray-100 text-gray-900"
: "rounded-md text-gray-700 ",
"block px-8 py-1 text-sm hover:bg-light-green hover:w-24 hover:text-white")
}
>
수정
</button>
)}
</Menu.Item>
<Menu.Item>
{({ active }) => (
<button
value={index}
onClick={onRemoveWord}
className={
(active
? "bg-gray-100 text-gray-900"
: "text-gray-700",
"block px-8 py-1 text-sm hover:bg-light-green hover:w-24 hover:text-white")
}
>
삭제
</button>
)}
</Menu.Item>
</div>
</Menu.Items>
</Transition>
</Menu>
</div>
</div>
</>
);
}
})}
{/* item end */}
</div>
</div>
{/* Easy end */}
{/* Middle start */}
<div className="group relative rounded-lg p-3 lg:w-80 lg:ml-10">
<div className="overflow-y-auto max-h-96 aspect-w-1 aspect-h-1 w-full overflow-hidden rounded-md bg-white border-2 border-light-green lg:aspect-none">
<div>
<h1 className="text-slate-900 font-medium px-3 pt-2">
🥈 Middle
</h1>
</div>
{/* <div className="flex absolute left-32 top-4">
<button className="hover:bg-gray-100 w-8 h-8 rounded-lg">
<ChevronLeftIcon onClick={onClickLeftMiddle} />
</button>
<div className="font-medium mt-1">{pageMiddle}</div>
<button className="hover:bg-gray-100 w-8 h-8 rounded-lg">
<ChevronRightIcon onClick={onClickRightMiddle} />
{minIndexMiddle} {maxIndexMiddle}
</button>
</div> */}
{/* item start */}
{wordLists.map((word, index) => {
if (
word.type === "middle"
{
return (
<>
<div
key={word}
className="flex items-start bg-gray-400 group-hover:opacity-80 rounded-lg m-2"
>
<div className="h-24 w-90 sm:600 w-96 lg:w-48">
<div className="flex py-5 pl-1">
<input
onClick={onClickSelected}
value={index}
id="checkItem"
name="checkItem"
type="checkbox"
className="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500"
/>
</div>
<li className="flex first:pt-0 last:pb-0">
<div className="relative bottom-10 ml-9 overflow-hidden">
<p className="text-sm font-medium text-slate-900">
{word.english} ({index})
</p>
<p className="text-sm text-slate-900 truncate">
{word.korean} {word.id} {word.status}
</p>
</div>
</li>
</div>
<div className="relative h-24 w-24 py-2">
<Menu
as="div"
className="relative inline-block text-left"
>
<div>
<Menu.Button className="inline-flex justify-center rounded-md border border-gray-300 bg-white px-1 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 focus:ring-offset-gray-100">
Options
<ChevronDownIcon
className="ml-1 h-5 w-5"
aria-hidden="true"
/>
</Menu.Button>
</div>
<Transition
as={Fragment}
enter="transition ease-out duration-90"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items className="absolute right-0 z-10 mt-2 w-24 origin-top-right rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
<div className="py-0">
<Menu.Item>
{({ active }) => (
<button
value={index}
onClick={onReviseWord}
className={
(active
? "rounded-md bg-gray-100 text-gray-900"
: "rounded-md text-gray-700 ",
"block px-8 py-1 text-sm hover:bg-light-green hover:w-24 hover:text-white")
}
>
수정
</button>
)}
</Menu.Item>
<Menu.Item>
{({ active }) => (
<button
value={index}
onClick={onRemoveWord}
className={
(active
? "bg-gray-100 text-gray-900"
: "text-gray-700",
"block px-8 py-1 text-sm hover:bg-light-green hover:w-24 hover:text-white")
}
>
삭제
</button>
)}
</Menu.Item>
</div>
</Menu.Items>
</Transition>
</Menu>
</div>
</div>
</>
);
}
}
})}
{/* item end */}
</div>
</div>
{/* Middle end */}
{/* Advance start */}
<div className="group relative rounded-lg p-3 lg:w-80 lg:ml-10">
<div className=" overflow-y-auto max-h-80 aspect-w-1 aspect-h-1 w-full overflow-hidden rounded-md bg-white border-2 border-light-green lg:aspect-none">
<div>
<h1 className="text-slate-900 font-medium px-3 pt-2">
🥇 Advance
</h1>
</div>
{/* item start */}
{wordLists.map((word, index) => {
if (word.type === "advance") {
return (
<>
<div className="flex items-start bg-gray-400 group-hover:opacity-80 rounded-lg m-2">
<div className="h-24 w-90 sm:600 w-96 lg:w-48">
<div className="flex py-5 pl-1">
<input
onClick={onClickSelected}
value={index}
id="checkItem"
name="checkItem"
type="checkbox"
className="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500"
/>
</div>
<li className="flex first:pt-0 last:pb-0">
<div className="relative bottom-10 ml-9 overflow-hidden">
<p className="text-sm font-medium text-slate-900">
{word.english} ({index})
</p>
<p className="text-sm text-slate-900 truncate">
{word.korean} {word.id} {word.status}
</p>
</div>
</li>
</div>
<div className="relative h-24 w-24 py-2">
<Menu
as="div"
className="relative inline-block text-left"
>
<div>
<Menu.Button className="inline-flex justify-center rounded-md border border-gray-300 bg-white px-1 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 focus:ring-offset-gray-100">
Options
<ChevronDownIcon
className="ml-1 h-5 w-5"
aria-hidden="true"
/>
</Menu.Button>
</div>
<Transition
as={Fragment}
enter="transition ease-out duration-90"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items className="absolute right-0 z-10 mt-2 w-24 origin-top-right rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
<div className="py-0">
<Menu.Item>
{({ active }) => (
<button
value={index}
onClick={onReviseWord}
className={
(active
? "rounded-md bg-gray-100 text-gray-900"
: "rounded-md text-gray-700 ",
"block px-8 py-1 text-sm hover:bg-light-green hover:w-24 hover:text-white")
}
>
수정
</button>
)}
</Menu.Item>
<Menu.Item>
{({ active }) => (
<button
value={index}
onClick={onRemoveWord}
className={
(active
? "bg-gray-100 text-gray-900"
: "text-gray-700",
"block px-8 py-1 text-sm hover:bg-light-green hover:w-24 hover:text-white")
}
>
삭제
</button>
)}
</Menu.Item>
</div>
</Menu.Items>
</Transition>
</Menu>
</div>
</div>
</>
);
}
})}
{/* item end */}
</div>
</div>
{/* Advance end */}
</div>
</div>
</>
);
};
export default WordList;
const initialState = {
wordLists: [
{
id: 2,
english: "red",
korean: "빨강",
type: "easy",
status: "A",
},
{
id: 4,
english: "blue",
korean: "파랑",
type: "easy",
status: "A",
},
{
id: 3,
english: "blue2",
korean: "파랑2",
type: "easy",
status: "A",
},
...
],
changeStatusWordLoading: false, //단어 상태 수정
changeStatusWordComplete: false,
changeStatusWordError: null,
};
//개별 status 바꾸기
changeStatusWordRequest: (state) => {
state.changeStatusWordLoading = true;
state.changeStatusWordError = null;
state.changeStatusWordComplete = false;
},
changeStatusWordSuccess: (state, action) => {
const wordInfo = action.payload;
state.changeStatusWordLoading = false;
state.changeStatusWordComplete = true;
state.wordLists[wordInfo.id].status = wordInfo.status;
},
changeStatusWordError: (state, action) => {
state.changeStatusWordLoading = true;
state.changeStatusWordError = action.error;
},
//전체 status 바꾸기
changeStatusWordAllRequest: (state) => {
state.changeStatusWordLoading = true;
state.changeStatusWordError = null;
state.changeStatusWordComplete = false;
},
changeStatusWordAllSuccess: (state, action) => {
**const wordInfo = action.payload;
state.changeStatusWordLoading = false;
state.changeStatusWordComplete = true;
//이 부분 적용
state.checkedWordList.length = 0;
state.wordLists.map((word, i) => {
word.status = wordInfo.status;
if (word.status === "C") {
state.checkedWordList.push(state.wordLists[i]);
} else if (word.status === "A") {
state.checkedWordList.pop();
}
});**
},
changeStatusWordAllError: (state, action) => {
state.changeStatusWordLoading = true;
state.changeStatusWordError = action.error;
},
export const {
changeStatusWordRequest,
changeStatusWordSuccess,
changeStatusWordError,
changeStatusWordAllRequest,
changeStatusWordAllSuccess,
changeStatusWordAllError,
} = wordSlice.actions;
function* changeStatus(action) {
try {
const data = action.payload;
yield put(changeStatusWordSuccess(data));
} catch (error) {
yield put(changeStatusWordError(error));
console.log(error);
}
}
function* changeStatusAll(action) {
try {
const data = action.payload;
yield put(changeStatusWordAllSuccess(data));
} catch (error) {
yield put(changeStatusWordAllError(error));
console.log(error);
}
}
function* change_Status_Req() {
yield takeLatest(changeStatusWordRequest.type, changeStatus);
}
function* change_StatusAll_Req() {
yield takeLatest(changeStatusWordAllRequest.type, changeStatusAll);
}
export const wordSagas = [
fork(change_Status_Req),
fork(change_StatusAll_Req),
];