본문 바로가기

TIL

개인과제 - MBTI 성격 유형 테스트 ③

발생한 문제 1

렌더링이 됐었는데 새로고침을 하니 오류가 발생했다.

 

🚨 문제 원인

첫 렌더링 값이 빈값(중첩된 객체X)이어서 오류가 발생했던 것이었다.

// 수정 전
return (
    <div>
      {testResult.map((obj) => {
        return (
          <div key={obj.id}>
            <h2>{obj.nickname}</h2>
            <span>{obj.date}</span>
            <p>{obj.result}</p>
          </div>
        );
      })}
    </div>
  );

 

✅ 해결 방법

testResult 의 길이가 0 이상일때만 렌더링되도록 삼항연산자로 조건을 추가해 렌더링 되도록 수정하니 새로고침을 해도 오류가 발생하지 않았다.

// 수정 후
 return (
    <div>
      {testResult.length > 0 ? (
        testResult.map((obj) => {
          return (
            <div key={obj.id}>
              <h2>{obj.nickname}</h2>
              <span>{obj.date}</span>
              <p>{obj.result}</p>
            </div>
          );
        })
      ) : (
        <div>테스트 데이터가 없습니다.</div>
      )}
    </div>
  );

 

 

발생한 문제 2

테스트 결과를 생성한 상태에 넣고 상태의 마지막 값을 렌더링하도록 구현했는데, 새로운 아이디로 로그인했을때 결과 창에 들어가니 이전 값이 보이는 문제가 발생했다.

(아직 테스트도 안했는데 이전 사용자의 테스트 결과 화면이 보임(배열의 마지막 값)) + 배열의 마지막 값을 보여줄 경우 동시 접속자가 동시에 제출했을때 올바른 값이 보이지 않을 수 있는 문제도 발생할 수 있음

기존 작성 코드 - TestResultPage 를 만들어 결과 제출을 눌렀을 때 페이지를 이동하고, getTestResults api 에서 값을 가져오는 함수를 호출한 값을 새로 생성한 상태에 넣어 가공해서 사용했다.

// 이전에 작성한 코드
const TestResultPage = () => {
  const [testResult, setTestResult] = useState({});

  useEffect(() => {
    const getResult = async () => {
      const result = await getTestResults();
      setTestResult(result);
    };
    getResult();
  }, []);

// testResult가 빈 값일 경우의 예외상황을 처리
  if (testResult.length === 0) {
    return <div>테스트 데이터가 없습니다.</div>;
  }
// testResult의 마지막 객체 (방금 제출한 테스트 결과)를 userResult 에 담아줌
  const userResult = testResult[testResult.length - 1];

  return (
    <div>
      <h1>
        {userResult.nickname}님의 테스트 결과 : {userResult.result}
      </h1>
      <p>설명</p>
      <button>결과 페이지로 이동하기</button>
    </div>
  );
};

export default TestResultPage;

 

✅ 해결 방법

TestResultPage를 생성해서 해당 페이지에서  api get 요청으로 결과를 보여주는것이 아니라, TestPage 에서 테스트폼 과 결과를 제출유무에 따라 조건부 렌더링하도록 하였다.

  // handleTestSubmit 함수에 넣어줄 초기값이 false 인 isTestDone 상태를 생성
  const [isTestDone, setIsTestDone] = useState(false);
  // handleTestSubmit 함수안의 resultData를 사용하기 위한 상태 생성
  const [result, setResult] = useState({});
  
  const handleTestSubmit = async (answers) => {
    const user = await getUser();
    const result = calculateMBTI(answers);
    const resultData = {
      userId: user.id,
      nickname: user.nickname,
      result,
      answers,
      date: new Date().toLocaleString(),
      visibility: true,
    };
    // resultData를 setResult 해주기
    setResult(resultData);
    await createTestResult(resultData);
    // 제출완료에 따라 조건부 렌더링을 하기위해 setIsTestDone에 true 넣어주기
    setIsTestDone(true);
  };

  return (
    <> // isTestDone이 fase 일 경우 TestForm 을 보여주고, true 이면 결과페이지를 보여주기
      {isTestDone === false ? (
        <div>
          <TestForm onSubmit={handleTestSubmit} />
        </div>
      ) : (
        <div>
          <div>
            <div>
              <h1>
                {result.nickname}님의 테스트 결과 : {result.result}
              </h1>
              <button
                onClick={() => navigate("/resultlist")}
              >
                결과 페이지로 이동하기
              </button>
            </div>
          </div>
        </div>
      )}
    </>
  );
};

 

 

■ 테스트 결과페이지에 description 적용하기

description 을 테스트 결과페이지에서 filter를 걸어 일치하는 mbti 의 설명을 가져왔다.

그런데 테스트 결과 리스트들을 보여주는 결과목록 페이지에도 description 을 적용하려고 하니 결과목록, description 둘다 길이가 긴 배열인데 어떻게 일치하는 mbti의 설명을 보여줄 수 있을지 좀 어려웠다.

// 처음에 만든 descriptions
export const mbtiDescriptions = [
  {
    id: 1,
    mbti: "INFJ",
    title: "예언자형",
    contents:
      "인내심이 많고 통찰력과 직관력이 뛰어나며 양심이 바르고 화합을 추구한다. 창의력과 통찰력이 뛰어나며, 강한 직관력으로 말없이 타인에게 영향력을 끼친다. 독창성과 내적 독립심이 강하며, 확고한 신념과 열정으로 자신의 영감을 구현시켜 나가는 정신적 지도자들이 많다. 직관력과 사람중심의 가치를 중시하는 분야 즉, 성직, 심리학, 심리치료와 상담, 예술과 문학분야이다. 테크니칼한 분야로는 순수과학, 연구 개발분야로써 새로운 시도에 대한 열성이 대단하다. 한 곳에 몰두하는 경향으로 목적달성에 필요한 주변적인 조건들을 경시하기 쉽고, 자기 안의 갈등이 많고 복잡하다. 이들은 풍부한 내적인 생활을 소유하고 있으며 내면의 반응을 좀처럼 남과 공유하기 어려워한다.",
  },
  ... ]
  
  // 테스트 결과 페이지에 filter로 적용
   const getMbtiContent = mbtiDescriptions.filter(
    (descript) => descript.mbti === result.result
  );

 

구현방법

desciption을 이전에 만들었던 배열안 객체형태가 아닌, key - mbti 값 : value-일치하는 설명이 담긴 객체 형태로 수정했다.

테스트 결과 페이지는 기존 filter를 사용하던 코드를 삭제하고 description: description[result] 을 추가해서 값을 사용했고

테스트 결과 목록 페이지는 result를 map으로 순회 해서 mbit가 일치하는 description을 넣었고 화면에 ui 를 렌더링할때는 testResult 를 map으로 순회해서 값을 사용했다.

// 수정한 desciption
export const description = {
  INFJ: {
    id: 1,
    title: "예언자형",
    contents:
      "인내심이 많고 통찰력과 직관력이 뛰어나며 양심이 바르고 화합을 추구한다. 창의력과 통찰력이 뛰어나며, 강한 직관력으로 말없이 타인에게 영향력을 끼친다. 독창성과 내적 독립심이 강하며, 확고한 신념과 열정으로 자신의 영감을 구현시켜 나가는 정신적 지도자들이 많다. 직관력과 사람중심의 가치를 중시하는 분야 즉, 성직, 심리학, 심리치료와 상담, 예술과 문학분야이다. 테크니칼한 분야로는 순수과학, 연구 개발분야로써 새로운 시도에 대한 열성이 대단하다. 한 곳에 몰두하는 경향으로 목적달성에 필요한 주변적인 조건들을 경시하기 쉽고, 자기 안의 갈등이 많고 복잡하다. 이들은 풍부한 내적인 생활을 소유하고 있으며 내면의 반응을 좀처럼 남과 공유하기 어려워한다.",
  },
  ... }
  
  // 테스트 결과 페이지
  const handleTestSubmit = async (answers) => {
    const user = await getUser();
    const result = calculateMBTI(answers);
    const resultData = {
      userId: user.id,
      nickname: user.nickname,
      result,
      answers,
      date: new Date().toLocaleString(),
      visibility: true,
      description: description[result], //result(mbti)의 값을 description에 추가
    };
    console.log("resultData :>> ", resultData);
    setResult(resultData);
    await createTestResult(resultData);
    setIsTestDone(true);
  };
  
  // 테스트 결과 목록 페이지
  useEffect(() => {
    const getResult = async () => {
      const result = await getTestResults();
	// result를 map으로 순회하여 각 결과에 mbit가 일치하는 description을 넣어주기
      setTestResult(
        result.map((item) => ({
          ...item,
          description: description[item.result],
        }))
      );
    };
    getResult();
  }, []);
  
  return (
    <div className="flex flex-col min-h-full items-center py-12">
      {testResult.length > 0 ? (
        testResult.map((obj) => {
          return (
            <div
              key={obj.id}
              className="bg-gray-800 text-white w-2/6 p-5 m-5 rounded-2xl"
            >
              <div className="flex justify-between">
                <p>{obj.nickname}</p>
                <p>{obj.date}</p>
              </div>
              <p className="text-yellow-400">{obj.result}</p>
              <p>{obj.description?.contents}</p>
            </div>
          );
        })
      ) : (
        <div>테스트 데이터가 없습니다.</div>
      )}
    </div>
  );

 

느낀점

이번 과제에서는 인증/인가 부분과 json-server, 백엔드 서버를 활용해서 구현을 해야했다. 강의를 들을땐 이런내용이구나~ 했는데 막상 코드를 작성하려고 하니 너무 어려웠다. 이부분도 여러번 해보다보면 익숙해지겠지.. 아직 사용자에 맞게 삭제, 공개 버튼과 도전과제였던 Tanstack Query 사용, 코드 리팩토링은 하지도 못했지만 짬짬이 해봐야지