url은 다음과 같다.
<Route path="/quiz/:title/:chapter" element={<Quiz/>}/>
<Route path="/activity/:title/:chapter" element={<Activity/>}/>
단원별 활동은 사용자가 책을 한 단원 읽을 때마다 해당 단원에 해당하는 내용을 퀴즈를 풀면서 다시 한번 복기하고 짧은 글 쓰기 활동을 통해 독후 감상문을 한번에 작성하는 부담을 줄일 수 있도록 합니다.
퀴즈 활동 관련한 요구사항을 정의할 때 처음에는 단원 별 문제의 개수를 5개로 고정했습니다. API도 각 문제를 일일이 호출 하고 응답 받는 식으로 만들어지기도 했고.. 그런데 컴퓨터 공학에서 고정이라는 말은 기피하는 것이 좋은데..역시나 프로젝트를 진행하면서 문제의 개수를 5개로 고정시킨 것이 문제가 되어 API 설계와 프론트 코드도 거의 싹 갈아 엎는 상황이 발생했습니다. API는 문제를 일일이 호출해서 받아오는 방식이 아니라 한번에 해당 단원에 해당하는 문제를 모두 받아오는 방식으로 바꾸었습니다.
useEffect(() => {
let quiztemp = [];
if(flag){
axios
.get(`${BASE_URL}quiz/${title}/${currentChapter}/?quiz_number=${1}`, {
headers: {
Authorization: TOKEN,
},
})
.then((e) => {
quiztemp.push(e.data)
})
.catch((e) => {setQuizs(quiztemp); setNumbers([]); setFlag(false);setLoading(false)});
}
if(flag){
axios
.get(`${BASE_URL}quiz/${title}/${currentChapter}/?quiz_number=${2}`, {
headers: {
Authorization: TOKEN,
},
})
.then((e) => {
quiztemp.push(e.data)
})
.catch((e) => {setQuizs(quiztemp); setNumbers(['일 번']); setFlag(false);setLoading(false)});
}
if(flag){
axios
.get(`${BASE_URL}quiz/${title}/${currentChapter}/?quiz_number=${3}`, {
headers: {
Authorization: TOKEN,
},
})
.then((e) => {
quiztemp.push(e.data)
})
.catch((e) => {setQuizs(quiztemp); setNumbers(['일 번','이 번']);setFlag(false); setLoading(false)});
}
if(flag){
axios
.get(`${BASE_URL}quiz/${title}/${currentChapter}/?quiz_number=${4}`, {
headers: {
Authorization: TOKEN,
},
})
.then((e) => {
quiztemp.push(e.data)
})
.catch((e) => {setQuizs(quiztemp);setNumbers(['일 번','이 번','삼 번']); setFlag(false);setLoading(false)});
}
if(flag){
axios
.get(`${BASE_URL}quiz/${title}/${currentChapter}/?quiz_number=${5}`, {
headers: {
Authorization: TOKEN,
},
})
.then((e) => {
setNumbers(['일 번','이 번','삼 번','사 번','오 번'])
quiztemp.push(e.data)
setQuizs(quiztemp)
setLoading(false)
})
.catch((e) => {setQuizs(quiztemp); setNumbers(['일 번','이 번','삼 번','사 번']);setFlag(false);setLoading(false)});
}
}, []);
useEffect(() => {
axios.get(`${BASE_URL}quiz/${title}/${currentChapter}/all`, {
headers: {
Authorization: TOKEN,
},
})
.then((res) => {
console.log(res.data);
setQuizs(res.data.results)
setNumbers(['일 번','이 번','삼 번','사 번','오 번'].slice(0,res.data.count))
setLoading(false)
});
}, []);
팝업창을 띄우는 방식은 앞에서 설명한 동적인 UI를 설계하는 것과 동일하다.
먼저 html,css를 만들어 넣고 조건에 따라 state를 바꿔주면 된다. 문제를 틀렸을 때랑 맞았을 때 나오는 텍스트와 밑에 progress bar 색깔이 다른데 이것도 마찬가지로 조건에 따라 실행되는 html, css 코드를 다르게 설계하면 된다.
if (res.data == "정답입니다!") {
let temp = [...imgShow];
temp[answerClicked] = true;
setAnswer(answerClicked);
setImgShow(temp);
// 정답일 때 PopUpText설정
setPopuptext("정답입니다. 잘 했어요!");
// ref,ref2로 참조하고 있는 컴포넌트에 class 추가하여 팝업창이 나오도록.
ref.current.classList.add(`${styles.open}`);
ref2.current.classList.add(
`${styles.progressbaropen}`
);
// 시간이 지나고 팝업창이 없어지도록.
setTimeout(() => {
ref.current.classList.remove(`${styles.open}`);
ref2.current.classList.remove(
`${styles.progressbaropen}`
);
}, 3000);
if (answerstate[clicked] == false) {
setProgress(progress + (16 * 5) / quizs.length);
let temp = [...answerstate];
temp[clicked] = true;
setAnswerstate(temp);
setQuestioncount(questionCount + 1);
}
} else {
let temp = [...imgShow];
temp[answerClicked] = true;
setImgShow(temp);
setWrongcount(wrongcount + 1);
setHint(res.data.hint.join(" "));
// 틀렸을 경우 PopUpText 설정
setPopuptext("한번더 생각해보세요!");
ref.current.classList.add(`${styles.open}`);
ref2.current.classList.add(
`${styles.progressbaropenwrong}`
);
setTimeout(() => {
ref.current.classList.remove(`${styles.open}`);
ref2.current.classList.remove(
`${styles.progressbaropenwrong}`
);
}, 3000);
}
}
단원별 쓰기 활동은 크게 4가지로 구성되어있다. 단원 별 핵심 단어(키워드) 작성, 그렇게 생각한 이유, 단원별 요약 작성, 단원별 느낌점 작성이다. 그리고 처음에 설계한 요구사항은 사용자가 글을 작성하다가 돌아가기 버튼을 누르고 나가도 다음에 작성할 때 이어서 작성할 수 있도록 하고 작성하는 상황에서도 다른 작성 탭으로 이동했다가 돌아와도 이어서 작성하는 것을 원했다.
처음에는 단순히 textarea 태그를 하나만 이용하고 작성한 내용을 state에 저장하여 탭을 변경할 때마다 textarea에 들어가는 value를 해당 state에 저장된 값으로 바꿔주었다. 그런데 이 방식은 비동기 때문에 내가 원하는 방식으로 작동이 되지 않았다. 그래서 textarea 태그를 4개를 겹쳐 두고 탭을 누르면 해당 textarea의 z-index 값을 바꾸어 가장 앞에 보이게끔 하였다.
ref1.current.classList.remove(`${styles.zindex}`);
ref2.current.classList.remove(`${styles.zindex}`);
ref3.current.classList.remove(`${styles.zindex}`);
ref4.current.classList.remove(`${styles.zindex}`);
ref1_1.current.classList.remove(`${styles.ondisplay}`);
ref2_1.current.classList.remove(`${styles.ondisplay}`);
ref3_1.current.classList.remove(`${styles.ondisplay}`);
ref4_1.current.classList.remove(`${styles.ondisplay}`);
if (clicked == 0) {
ref1.current.classList.add(`${styles.zindex}`);
ref1_1.current.classList.add(`${styles.ondisplay}`);
ref1.current.value = keywordinput;
} else if (clicked == 1) {
ref2.current.classList.add(`${styles.zindex}`);
ref2_1.current.classList.add(`${styles.ondisplay}`);
ref2.current.value = reasoninput;
} else if (clicked == 2) {
ref3.current.classList.add(`${styles.zindex}`);
ref3_1.current.classList.add(`${styles.ondisplay}`);
ref3.current.value = summaryinput;
} else if (clicked == 3) {
ref4.current.classList.add(`${styles.zindex}`);
ref4_1.current.classList.add(`${styles.ondisplay}`);
ref4.current.value = feelinginput;
}
}
}