data 뿌려줌 (get api 호출) -> 사용자가 data 업데이트 혹은 삭제(delete or post api 호출) -> DB에 데이터 전송 후, 변경된 data 다시 뿌려줌(get api 호출 다시)
이 동기적 작업을 프로젝트 내내 반복했는데 할때마다 매번 새롭고 매번 헤메고 있어서 삽질의 결과를 정리해두고자 함
근데 이 방법도 사실은 좀 안좋은 방식이 아닌가.. 하긴 함 ㅜㅜ
1. api로 받아온 데이터를 저장하는 state와 이를 복사해서 컴포넌트에 전달할 state를 별도로 설정함
// load chapters
const [chapters, setChapters]=useState([]);
// chapter edit component control
const [chapterList, setChapterList]=useState([]);
유튜브 처럼 챕터를 저장해놓고 해당 챕터별로 이동하는 기능을 구현하는 하려고 함
중요한건 state 이름은 아니고 컴포넌트에 전달해주는 state 와 api로 받아와준 state를 나눠서 두 개를 비교해서 변경을 감지해줄 것임
chapter : DB에서 받아온 data, 여기에 변경이 감지되는 경우 랜더링 되도록 할것임
chapterlist: 다른 컴포넌트에 전달할 data를 저장하는 곳
2. useEffect 훅 함수를 사용해서 변경이 감지될때마다 api를 재호출해줌
// 맨 처음 마운트 될 때 데이터를 불러와주고 있음
useEffect(()=>{
loadChaptersFn();
}, [])
// chapters의 정보가 존재하면
// chapterlist에 전달해줌
useEffect(()=>{
const makeChapterListFromChapters= async (chapters)=>{
const list=[]
for(let {chapterTitle, value} of await chapters){
list.push({time:value, title: chapterTitle})
}
setChapterList(list)
}
makeChapterListFromChapters(chapters);
console.log(chapters)
console.log(chapterList)
},[chapters])
const loadChaptersFn= async () =>{
await axios.get(`/api/chapters/${contentId}`).then(async (res) => {
setChapters(res.data.result);
});
}
// 실질적으로 컴포넌트에서 반영되는 data는 chapterList state 임
return (
<>
<StyledEngineProvider injectFirst>
<Header>
<UserContent
chapterList={chapterList}
handleValueChangeFn={handleValueChangeFn}
submitValueFn={submitValueFn}
/>
</Header>
</StyledEngineProvider>
</>
);
사용자가 내용을 업데이트해서 제출할때마다 loadChaptersFn을 다시 호출할 것이고
그때마다 chapters가 내용이 바뀔때마다 랜더링 해주는 useEffect가 화면을 새로운 내용으로 업데이트 해줄 것임.
2-1. 사용 예시
const handleValueChangeFn = (e, idx)=>{
let {name, value}= e.target;
if(name==='time'){
const timeArr=value.split(':');
console.log(timeArr)
let hour=0;
let sec=0;
let min=0;
let newValue=0;
switch(timeArr.length){
case 2:
min= minutesToSeconds(timeArr[0]);
sec= parseInt(timeArr[1]);
newValue=min+sec;
console.log(newValue)
break;
case 3:
hour= hoursToSeconds(timeArr[0]);
min= minutesToSeconds(timeArr[1]);
sec= parseInt(timeArr[2]);
newValue=hour+min+sec;
console.log(newValue)
break;
default:
newValue=0;
}
value=newValue;
}
const list= [...chapterList];
list[idx][name]=value;
setChapterList(list)
};
// 제출버튼에 들어갈 function
const submitValueFn = async () =>{
const resultMap= new Map();
chapterList.map((value)=>{
resultMap.set(`${value.time}`, `${value.title}`);
});
chapterService.createChapters(contentId, resultMap).then(()=>{
loadChaptersFn();
handleBarOpen('정상적으로 등록되었습니다.')
})
};
중간에 handleValueChangeFn이 쓸데없이 긴데, 중간 생략하고 내용만 보면
사용자가 value값을 입력할 때마다 chapterList의 내용을 변경해주는 역할임
[...chapterlist] 얕은 복사로 만약 chapterlist 내용이 이미 존재한다면 같은 data를 가진 상태에서
새로운 내용을 추가하거나 아니면 기존 내용을 삭제하거나 하기 위함임
그리고 최종적으로 submitValueFn 부분에서 사용자가 제출버튼을 누르면
업데이트 된 chapterList의 내용을 api request 모양에 맞춰 (map 형식으로 전달해야했음) 가공한 후 전달해주고
완료가 되었을때만 2-1에서 설명한 것처럼 새롭게 loadChaptersFn을 호출해주면 됨
3. setTimeout 함수로 시간차 공격하기
참 안좋은 방식인 것 같지만 어쨌든 임시적으로 해결한 방법이긴 하다
redux toolkit 으로 contents 를 저장해서 꺼내는 방식으로 사용했는데 (근데 지금보니 굳이 store에 저장해서 쓸 필요가 없었던것 같긴하다; 나중에 확장하려면 필요하긴 했겠지만 당장은 불필요했음)
data 뿌려줌 (get api 호출) -> 사용자가 data 업데이트 혹은 삭제(delete or post api 호출) -> DB에 데이터 전송 후, 변경된 data 다시 뿌려줌(get api 호출 다시)
이 흐름을 api 호출이 아니라 store에 있는 state 값들을 업데이트 해야하는데 사용자가 data 변경 api 를 호출 한 후!에 다시 store를 업데이트 하는 동기적으로 잡기가 어려워서 강제로 setTimeout을 사용해서 시간이 지난 후에 store값을 업데이트 하도록 했다.
// paginaion 적용
const { contents, pagination } = useSelector((state) => state.content);
// load contents fn
const loadContentsFn= (page, categoryId, searchTitle, searchTags, size)=>{
dispatch(loadContents([page, categoryId, searchTitle, searchTags, size]));
}
useSeletor는 store에서 값을 불러와서 사용해 주면 되는데
사용자가 삭제 api를 호출해주고 새로운 content를 불러와야할때마다 loadContentsFn으로 store값을 업데이트 해준다.
3.1 사용 예시
const deleteContentsFn = () => {
const arr = [];
for (let a of checked) {
arr.push(a.contentId);
}
try {
contentService.deleteContents(arr).then((result) => {
setTimeout(()=>{loadContentsFn(page, categoryId, searchTitle, searchTags, size);}, 500)
});
} catch (e) {
console.log(e);
}
handleClose();
setDeleteBtn(false);
setChecked([]);
};
시간을 두지 않고 async로 해줘도 될것 같다...? 근데 dispatch 를 단독으로 사용하면 순서대로 실행되지가 않아서 결국 async 나 timeout 같은 function 으로 감싸서 사용해줬다.
단점: 500으로 시간을 두었지만 만약 5초안에 delete 가 되지 않는다면..? 업데이트가 안되겠지..
redux store의 값을 업데이트 해주는 방식으로는 이런 방식도 있다고 함
- immer 사용법 https://kyounghwan01.github.io/blog/React/immer-js/#immer-js%E1%84%85%E1%85%A1%E1%86%AB |
'개발자공부 (2021.11~현재) > React' 카테고리의 다른 글
사진(영상) 미리보기 & 드래그 앤 드랍 (0) | 2022.11.08 |
---|---|
비디오 플레이어 커스터마이징 하기 (0) | 2022.11.07 |
계층형 구조 카테고리 treeview 구현(react-dnd-treeview 외) (0) | 2022.11.03 |
리액트 로그인 유지 구현(스프링 부트 api 호출 방식) (0) | 2022.11.02 |
리덕스(redux) 이해하기 (0) | 2022.08.05 |