CreateQuestion.tsx
import { useState } from "react";
import { useNavigate } from "react-router-dom";
import { useRecoilState, useSetRecoilState } from "recoil";
import { Container, CommonComponent, Title, SubTitle, HeadTitle, PrimaryLargeButton, CheckSubComponent, Answer, BackCircle } from "../components/commons/Commons";
import { CommonInput } from "../components/commons/Input";
import { correctNum, incorrectNum, questionNum, questionSet } from "../utils/storage";
function CreateQuestion() {
// Question 갯수는 check 페이지에서도 써야하기 때문에 useRecoilState로 관리
const [allQuestion, setQuestionCount] = useRecoilState(questionNum);
// 문제와 답의 state
const [question, setQuestion] = useState('');
const [answer, setAnswer] = useState('');
const addQuestion = (e:React.ChangeEvent<HTMLInputElement>) => {
setQuestion(e.currentTarget.value);
}
const addAnswer = (e:React.ChangeEvent<HTMLTextAreaElement>) => {
setAnswer(e.currentTarget.value);
}
// 문제-답(1세트) state
const setSet = useSetRecoilState(questionSet);
// 문제 생성 버튼
const handleCreateQuestion = (question:string, answer:string) => {
if(question === '' || answer === '') {
alert("문제나 답을 다시 입력해주세요 :)");
return
}
setSet((oldSet) => {
const newSet = {id:Date.now(), question: question, answer: answer};
return [newSet, ...oldSet];
})
// 문제 갯수 counting
setQuestionCount((prevNum:number) => prevNum+1);
// 생각해봐야 할 점
// 1. 버튼을 누르면 textArea와 input의 있는 text들이 초기화 되어야 함.
setQuestion('');
setAnswer('');
window.location.reload();
}
const correctQuestionsNum = useSetRecoilState(correctNum);
const incorrectQuestionsNum = useSetRecoilState(incorrectNum);
// 리셋 버튼을 누를 시, 문제 갯수, 정답, 오답 갯수 모두 초기화
const handleResetButton = () => {
correctQuestionsNum(0);
incorrectQuestionsNum(0);
setQuestionCount(0);
setSet((prevQuestions) => {
return prevQuestions = [];
})
}
// 자가 점검 페이지로 이동
const selfCheckPage = useNavigate();
const goToSelfCheck = () => {
selfCheckPage("/self-check")
}
// 문제 수정 페이지로 이동
const modifyQuestionPage = useNavigate()
const goModifyQuestion = () => {
modifyQuestionPage("/modify");
}
// 뒤로가기 버튼
const moveBack = useNavigate();
const handleBackCircleClick = () => {
moveBack('/check');
}
return (
<Container style={{height: '150vh'}}>
<BackCircle
onClick = {handleBackCircleClick}
>
<img style={{marginRight:'2px', width: '50%'}} src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iNzUycHQiIGhlaWdodD0iNzUycHQiIHZlcnNpb249IjEuMSIgdmlld0JveD0iMCAwIDc1MiA3NTIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiA8ZGVmcz4KICA8Y2xpcFBhdGggaWQ9ImEiPgogICA8cGF0aCBkPSJtMjQzIDEzOS4yMWgyNjZ2NDczLjU4aC0yNjZ6Ii8+CiAgPC9jbGlwUGF0aD4KIDwvZGVmcz4KIDxnIGNsaXAtcGF0aD0idXJsKCNhKSI+CiAgPHBhdGggZD0ibTI1MS41OCAzNTQuNzMgMjA3LjAyLTIwNy4wMmMxMi4wNTEtMTEuMzQ0IDMwLjQ4NC0xMS4zNDQgNDEuODI4IDBzMTEuMzQ0IDI5Ljc3NyAwIDQxLjgyOGwtMTg2LjQ1IDE4Ni40NSAxODYuNDUgMTg2LjQ1YzExLjM0NCAxMS4zNDQgMTEuMzQ0IDMwLjQ4NCAwIDQxLjgyOHMtMjkuNzc3IDExLjM0NC00MS44MjggMGwtMjA3LjAyLTIwNy43MmMtMTEuMzQ0LTExLjM0NC0xMS4zNDQtMjkuNzc3IDAtNDEuODI4eiIgZmlsbC1ydWxlPSJldmVub2RkIi8+CiA8L2c+Cjwvc3ZnPgo=" />
</BackCircle>
<Title>CREATE QUESTION</Title>
<SubTitle>체크하고 싶은 문제를 만들어 보세요 :) </SubTitle>
<SubTitle>문제들을 다 풀었다면 문제 리셋을 한 후 "점검 하러 가기"로 이동해주세요 :) </SubTitle>
<CommonComponent style={{padding:'0px' ,width:'75%',height: '100vh'}}>
<HeadTitle>Create Question</HeadTitle>
<CheckSubComponent
>
<CommonInput
onChange={addQuestion}
style={{borderRadius: '10px', marginBottom:'20px'}}
placeholder="문제를 생성해 보세요 :)"
/>
<Answer
onChange={addAnswer}
placeholder="문제에 맞는 답을 적어 주세요 :)"/>
<SubTitle style={{color:'black', margin:'10px'}}> Question : {allQuestion} </SubTitle>
<PrimaryLargeButton
onClick={() => handleCreateQuestion(question, answer)}
>
문제 생성
</PrimaryLargeButton>
<PrimaryLargeButton
onClick={goModifyQuestion}>문제 수정
</PrimaryLargeButton>
<PrimaryLargeButton
onClick={handleResetButton}>문제 리셋
</PrimaryLargeButton>
<PrimaryLargeButton
onClick={goToSelfCheck}>점검 하러 하기
</PrimaryLargeButton>
</CheckSubComponent>
</CommonComponent>
</Container>
)
}
export default CreateQuestion;
import BackButton from "src/components/commons/BackButton";
import { Container, CommonComponent, Title, SubTitle, HeadTitle, CheckSubComponent } from "src/components/commons/Commons";
import Input from "src/components/create/atoms/Input";
import TextArea from "src/components/create/atoms/TextArea";
import QuestionNumber from "src/components/create/atoms/QuestionTitle";
import useLoadAllQuestion from "src/hooks/useLoadAllQuestion";
import useCreate from "src/hooks/useCreate";
import CreateButton from "src/components/create/atoms/CreateButton";
import MovePageButton from "src/components/create/atoms/MovePageButton";
import ResetButton from "src/components/create/atoms/ResetButton";
import useReset from "src/hooks/useReset";
export default function CreateQuestionComponent() {
const {questionNumber} = useLoadAllQuestion();
const {handleCreateQuestion, question, answer} = useCreate();
const {handleResetButton} = useReset();
return (
<Container style={{height: '150vh'}}>
<BackButton url={"/check"}/>
<Title>CREATE QUESTION</Title>
<SubTitle>체크하고 싶은 문제를 만들어 보세요 :) </SubTitle>
<SubTitle>문제들을 다 풀었다면 문제 리셋을 한 후 "점검 하러 가기"로 이동해주세요 :) </SubTitle>
<CommonComponent style={{padding:'0px' ,width:'75%',height: '100vh'}}>
<HeadTitle>Create Question</HeadTitle>
<CheckSubComponent>
<Input/>
<TextArea/>
<QuestionNumber questionNumber={questionNumber}/>
<CreateButton
createQuesitonFn = {handleCreateQuestion}
content = {"문제 생성"}
question = {question}
answer = {answer}
/>
<MovePageButton
url={"/modify"}
content={"문제 수정"}
/>
<ResetButton
resetFn={handleResetButton}
content={"문제 리셋"}
/>
<MovePageButton
url={"/self-check"}
content={"점검하러 가기"}
/>
</CheckSubComponent>
</CommonComponent>
</Container>
)
}
각 기능(전체 문제 개수, 문제 생성 함수, 리셋 함수)들을 custom hook으로 분리하여 SRP를 지키고자 함
느슨한 결합을 위해 Input, TextArea, QuestionNumber, CreateButton, ResetButton, MovePageButton을 atom으로 분리
atom 들을 CreateQuestionComponent에 결합
import { useSetRecoilState } from "recoil";
import { questionState } from "src/utils/storage";
import styled from "styled-components";
export const CommonInput = styled.input`
width: 70%;
height: 10%;
border-radius: 10px;
text-align: center;
border: 0px;
background-color: #E0E3E8;
box-shadow: 0 4px 6px rgba(50, 50, 93, 0.1), 0 1px 3px rgba(0, 0, 0, 0.05);
margin-bottom: 20px;
margin-top : 10px;
`
interface InputProps {
changeFn : (e: React.ChangeEvent<HTMLInputElement>) => void;
}
export default function Input ({changeFn} : InputProps) {
return (
<CommonInput
onChange={changeFn}
placeholder="문제를 생성해 보세요 :)"
/>
)
}
import styled from "styled-components";
export const Answer = styled.textarea`
width: 70%;
height: 70px;
border : 0px;
box-shadow: 0 4px 6px rgba(50, 50, 93, 0.15), 0 1px 3px rgba(0, 0, 0, 0.08);
border-radius: 10px;
text-align: center;
padding: 25px 0px;
margin-bottom: 20px;
resize : none;
`;
interface TextAreaProps {
changeFn : (e: React.ChangeEvent<HTMLTextAreaElement>) => void;
}
export default function TextArea({changeFn} : TextAreaProps) {
return (
<Answer
onChange={changeFn}
placeholder="문제에 맞는 답을 적어 주세요 :)"
/>
)
}
import { useSetRecoilState } from "recoil";
import { answerState, questionState } from "src/utils/storage";
export default function useQuestionSet() {
const setQuestion = useSetRecoilState(questionState);
const addQuestion = (e:React.ChangeEvent<HTMLInputElement>) => {
setQuestion(e.currentTarget.value);
}
const setAnswer = useSetRecoilState(answerState);
const addAnswer = (e:React.ChangeEvent<HTMLTextAreaElement>) => {
setAnswer(e.currentTarget.value);
}
return {
addQuestion,
addAnswer
}
}
import { SubTitle } from "src/components/commons/Commons"
interface QuestionNumberProps {
questionNumber : number
}
export default function QuestionNumber({questionNumber} : QuestionNumberProps) : React.ReactElement {
return (
<SubTitle style={{color:'black', margin:'10px'}}>
Question : {questionNumber}
</SubTitle>
)
}
import { useRecoilValue } from "recoil";
import { questionNum } from "src/utils/storage";
export default function useLoadAllQuestion() {
const questionNumber = useRecoilValue(questionNum);
return {
questionNumber,
}
}
import { useNavigate } from "react-router-dom";
import { LargeButton } from "./CreateButton";
interface MovePageButtonProps {
content : string;
url : string;
}
export default function MovePageButton({content, url} : MovePageButtonProps) {
const navigate = useNavigate();
const movePage = () => {
navigate(url)
}
return(
<LargeButton
onClick={movePage}
>
{content}
</LargeButton>
)
}
import styled from "styled-components"
export const LargeButton = styled.button`
font-size: 20px;
margin-top: 18px;
width: 30%;
border: 0px;
height: 60px;
border-radius: 10px;
box-shadow: 0 4px 6px rgba(50, 50, 93, 0.15), 0 1px 3px rgba(0, 0, 0, 0.08);
background-color: #3C73CF;
color: white;
`
interface PrimaryLargeButtonProps {
createQuesitonFn : (question : string, answer : string) => void;
content : string
question : string,
answer : string
}
export default function CreateButton({ createQuesitonFn, content, question, answer } : PrimaryLargeButtonProps ) {
return (
<LargeButton
onClick={() => createQuesitonFn(question, answer)}
>
{content}
</LargeButton>
)
}
createQuestionFn, content, createQuestionFn에 인자로 들어가는 question, answer를 상위 컴포넌트로 부터 Props로 받음
Props의 타입은 Interface로 정의
LargeButton은 다른 컴포넌트 (move page button, reset button)에서 사용하기 때문에 export
import { useRecoilState, useSetRecoilState } from "recoil";
import { answerState, questionNum, questionSet, questionState } from "src/utils/storage";
export default function useCreate() {
const [question, setQuestion] = useRecoilState(questionState);
const [answer, setAnswer] = useRecoilState(answerState);
const setSet = useSetRecoilState(questionSet);
const setQuestionNumber = useSetRecoilState(questionNum)
const handleCreateQuestion = (question : string, answer : string) => {
if(question === '' || answer === '') {
alert("문제나 답을 다시 입력해주세요 :)");
return
}
setSet((oldSet) => {
const newSet = {id:Date.now(), question: question, answer: answer};
return [newSet, ...oldSet];
})
setQuestionNumber((prevNum:number) => prevNum+1);
setQuestion('');
setAnswer('');
window.location.reload();
}
return {
handleCreateQuestion,
question,
answer
}
}
import { LargeButton } from "./CreateButton";
interface ResetButtonProps {
resetFn : () => void;
content : string
}
export default function ResetButton({ resetFn, content } : ResetButtonProps ) {
return (
<LargeButton
onClick={resetFn}
>
{content}
</LargeButton>
)
}
useReset.ts
import { useSetRecoilState } from "recoil";
import { correctNum, incorrectNum, questionNum, questionSet } from "src/utils/storage";
export default function useReset() {
const setQuestionNumber = useSetRecoilState(questionNum);
const correctQuestionsNum = useSetRecoilState(correctNum);
const incorrectQuestionsNum = useSetRecoilState(incorrectNum);
const setSet = useSetRecoilState(questionSet);
const handleResetButton = () => {
correctQuestionsNum(0);
incorrectQuestionsNum(0);
setQuestionNumber(0);
setSet((prevQuestions) => {
return prevQuestions = [];
})
}
return {
handleResetButton
}
}