21.11.18~21.11.28 리엑트 직업심리검사 프로젝트 진행 중 이슈들

2021. 11. 28. 15:25작업/React

 

 

11/18 이슈

import { useState, useEffect } from "react";
import { Link } from "react-router-dom";
import React from "react";
import axios from "axios";

function TestPage() {
  const [progress, setProgress] = useState(0);
  const [saveData, setSaveData] = useState([]);
  const [userAnswer, setUserAnswer] = useState();
  const [totalAnswer, setTotalAnswer] = useState([]);
  const [totalScore, setTotalScore] = useState(0);

  useEffect(() => {
    //컴포넌트가 렌더링 될 떄마다 특정 작업을 실행할 수 있도록
    axios
      .get(
        "https://www.career.go.kr/inspct/openapi/test/questions?apikey=8611fd29678269e033bf421a0db5f770&q=6"
      )
      .then((res) => {
        console.log(res);
        setSaveData(res.data.RESULT);
        setTotalAnswer(Array(saveData.length));
      })
      .catch((err) => {
        console.log(err);
      }); //한번만 실행 원하면 ({함수},[]) 리렌더링시마다 실행하고싶으면 ({함수}) 특정 state가 바뀔떄마다 실행 ({함수},[name])
  }, []);
  return (
    <div>
      <header>검사 진행</header>
      <h2>{progress}% 진행 중</h2>
      <div className="question">
        {saveData.map((data) => {
          function inputHandler(e) {
            setUserAnswer(e.target.value);
            totalAnswer[Number(data.qitemNo) - 1] = userAnswer;
            setTotalScore(totalScore + Number(userAnswer));
            console.log(totalAnswer);
            console.log(totalScore);
          }
          return (
            <div key={data.qitemNo}>
              <p>{data.question}</p>
              <div>
                <input
                  type="checkbox"
                  value="1"
                  onChange={inputHandler}
                ></input>
                {data.answer01} : {data.answer03}
              </div>
              <div>
                <input
                  type="checkbox"
                  value="2"
                  onChange={inputHandler}
                ></input>
                {data.answer02} : {data.answer04}
              </div>
            </div>
          );
        })}
      </div>
      <button
        onClick={() => {
          setProgress(progress - 10);
          console.log(progress);
        }}
      >
        이전
      </button>
      <button
        onClick={() => {
          setProgress(progress + 10);
          console.log(progress);
        }}
      >
        다음
      </button>
    </div>
  );
}

export default TestPage;

이 코드에서 실행을 시켰더니 다음과 같은 화면이 나왔습니다.

그리고 여기서 사용자가 체크한 설문조사에 따라 점수가 부여되는데(심리검사 점수 매우그렇다1점 매우그렇지않다 5점과 같이) 그 점수를 totalScore에 저장하려고 합니다. 그리고 사용자가 입력한 값은 value로 하여 totalAnswer에 저장하려고 합니다. 그런데 위 코드와 같이 실행하였더니 totalScore가 Number() 처리를 해주었는데도 불구하고 Nan 으로 저장됩니다.

또한 사용자의 체크박스 체크에 따라 totalAnswer는 array로 저장하려고 하는데 각 question의 id인 qitemNo에 따라 totalAnswer를 indexing 을 했는데 totalAnswer의 배열에 하나 늦게 응답이 들어가는 것 같습니다.(즉 맨 처음 값은 무조건 undefined)가 섞입니다.

 

일단 지금 처리하고 싶은 사항은 두 가지가 있습니다.

Q1. 저기서 전체 총 saveData의 길이는 28인 Array인데, 전체 데이터는 saveData에 저장하고 총 28번까지 있는 saveData에서 1~5번까지 끊고 다음 버튼을 누르면 <div className="question"> 부분만 5개씩 리렌더링되게 하려면 어떻게 해야 할까요?

Q2. totalScore이 Nan으로 표시되는 문제, totalAnswer의 맨 처음값은 무조건 undefined가 섞이는 문제..

감사합니다.

 

답변

1. page라는 state를 만드시고, saveData 배열의 slice 메소드를 이용해서 구현하시면 될 것 같아요!

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/slice

 

Array.prototype.slice() - JavaScript | MDN

slice() 메서드는 어떤 배열의 begin부터 end까지(end 미포함)에 대한 얕은 복사본을 새로운 배열 객체로 반환합니다. 원본 배열은 바뀌지 않습니다.

developer.mozilla.org

2. setUserAnswer(e.target.value); 가 실행된 직후에 userAnswer 값을 가져오게되면 의도한 값이 나오지 않게됩니다.

setState 액션이 완료되기 전에 해당 state를 가져오려고 시도하기 때문입니다.

totalScore, progress 등은 totalAnswer 값이 바뀔 때마다 변경될 수 있도록 useEffect를 이용해보세요.

 

 


11/19 이슈 - DB? 사용 문의

 

state만 써서 사용자의 이름,성별,문항체크결과를 받기가 좀 번거로울 것 같은데 혹시 다음과 같이 json-server라는 서버를 npm install하고 data.json 파일을 만들어서 새로고침하거나 창을 닫아도 파일이 유지되게끔 해도 괜찮나요?

 

1. 파일구성

2. json-server이용해서 현재 리엑트가 돌아가는 3000번이 아닌 다른 포트 (예를 들면 http://localhost:3001/데이터) 에서 json 돌리기

React axios.put 또는 post나 fetch put,post 이용하여 값 넣은 후 data.json의 모습

3. http://localhost:3001/데이터 URL에다 값 넣고, 빼고 가져오고 하기

이 방식으로 하면 react를 구동하고 있는 3000번 포트를 끄거나 창을 닫고, 새로고침을 해도 값들이 저 data.json 파일에 변경사항이 보존되더라고요.

이런 방식으로 state만 쓰지 말고 data.json 사용해서 현재 제공되는 openAPI 제외하고도 또다른 서버인 json-server를 이용하여 localhost:3001 이랑 통신해서 사용자의 이름,성별,문항체크결과 등을 넣고 빼고 마음대로 사용해도 괜찮을까요? 또한 배포시에도 문제 없는지 궁금합니다.

감사합니다!

 

답변

별도로 서버를 구축해서 데이터를 임시저장하는 것 역시 좋은 접근입니다.

하지만 엘리스에서 이번 개인 프로젝트에서는 프론트엔드의 역량을 평가하는 단계이기 때문에 별도의 백엔드 서버를 구축하는 것을 권장하지 않고 있습니다.

원하시는 것은 브라우저의 LocalStorage를 이용해서 충분히 구현할 수 있을 것 같습니다.

https://ko.javascript.info/localstorage

을 참고해보세요.

 


11/18 이슈 2

 

import { useState, useEffect } from "react";
import { Link } from "react-router-dom";
import React from "react";
import axios from "axios";

function TestPage() {
  const [progress, setProgress] = useState(0);
  const [saveData, setSaveData] = useState([]); //length 28인 Array
  const [userAnswer, setUserAnswer] = useState();
  const [totalAnswer, setTotalAnswer] = useState([]);
  const [totalScore, setTotalScore] = useState(0);
  const [currentData, setCurrentData] = useState([, , , ,]);
  useEffect(() => {
    //컴포넌트가 렌더링 될 떄마다 특정 작업을 실행할 수 있도록
    axios
      .get(
        "https://www.career.go.kr/inspct/openapi/test/questions?apikey=8611fd29678269e033bf421a0db5f770&q=6"
      )
      .then((res) => {
        console.log(res);
        setSaveData(res.data.RESULT);
        setTotalAnswer(Array(saveData.length));
        for (let i = 0; i <= saveData.length - 1; i++) {
          for (let j = 0; j <= currentData.length - 1; j++) {
            currentData[i] = saveData[i];
          }
        }
        console.log(currentData);
      })
      .catch((err) => {
        console.log(err);
      }); //한번만 실행 원하면 ({함수},[]) 리렌더링시마다 실행하고싶으면 ({함수}) 특정 state가 바뀔떄마다 실행 ({함수},[name])
  }, []);
  function PlusProgress({ saveData, progress }) {
    setProgress(progress + 100 / (28 / 5));
    if (progress > 100) {
      setProgress(100);
    }
    console.log(progress);
  }
  function MinusProgress({ saveData, progress }) {
    setProgress(progress - 100 / (28 / 5));
    if (progress < 0) {
      setProgress(0);
    }
    console.log(progress);
  }
  return (
    <div>
      <header>검사 진행</header>
      <h2>{progress}% 진행 중</h2>
      <div className="question">
        {currentData.map((data) => {
          function inputHandler(e) {
            setUserAnswer(e.target.value);
            totalAnswer[Number(data.qitemNo) - 1] = userAnswer;
            setTotalScore(totalScore + Number(userAnswer));
            console.log(totalAnswer);
            console.log(totalScore);
          }
          return (
            <div key={data.qitemNo}>
              <p>{data.question}</p>
              <div>
                <input
                  type="checkbox"
                  value="1"
                  onChange={inputHandler}
                ></input>
                {data.answer01} : {data.answer03}
              </div>
              <div>
                <input
                  type="checkbox"
                  value="2"
                  onChange={inputHandler}
                ></input>
                {data.answer02} : {data.answer04}
              </div>
            </div>
          );
        })}
      </div>
      <button onClick={MinusProgress}>이전</button>
      <button onClick={PlusProgress}>다음</button>
    </div>
  );
}

export default TestPage;

실행화면

궁금한 점 Q1.

<header>검사 진행</header>
      <h2>{progress}% 진행 중</h2>
      <div className="question">

      <button onClick={MinusProgress}>이전</button>
      <button onClick={PlusProgress}>다음</button>

와 saveData(28개의 질문 받아오기)는 계속 그대로 남기고 가운데 질문 부분 <div className="question"> 만 다음/이전 버튼을 누를 떄마다 data를 5개씩 담아 렌더링 시키려고 했는데 보시다시피 전체 28개의 데이터를 처음 axios.get해오는 saveData에서 5개씩 for문으로 끊어서 가져오는 currentData가 그냥 길이가 4인 배열로 있습니다. 길이가 5가 아닌 4인 것도 문제인데, 아예 currentData가 출력이 되지 않습니다. 어떻게 해야 질문을 5개씩 받아올 수 있을까요? 아니면 아예 question부분만 재랜더링 하지말고 전체 페이지를 다른 페이지로 link 시키는 게 나을까요?

즉, saveData(28개의 질문 받아오기)는 계속 그대로 남기고 가운데 질문 부분 <div className="question"> 만 다음/이전 버튼을 누를 떄마다 data를 5개씩 담아 렌더링 시키려면 link 나 변수를 어떻게 짜야 할까요?

Q2. 현재 설문진행상황이 몇 퍼센트인지 표시하기 위한 progress변수를 정의했고 다음/이전 페이지 버튼을 누를때마다 progress는 0%에서 시작하여 100%로 증가하는데 총 28개의 질문을 5개씩 나누어 다음 페이지 버튼을 누르면 퍼센트가 증가하므로 progress + 100/(28/5) 로 설문조사진행율을 계산하는 함수 MinusProgress와 PlusProgress를 지정하고 다음/이전 페이지의 버튼의 onClick 함수로 넣어주었습니다. 그런데 그것을 숫자가 아닌 Nan으로 받는 것 같습니다. 무엇이 문제인 걸까요..

감사합니다.

 

답변

  1. currentData 는 state이기 때문에 값을 직접 수정하시면 안 됩니다.
  2. 클릭할 때마다 progress에 임의로 숫자를 더하거나 빼지 마시고, useEffect 혹은 useMemo를 활용해서 사용자가 입력한 값이 바뀔 때마다 progress가 자동으로 계산되도록 작성해보세요.

11/22 state 객체 중복 제거 이슈

안녕하세요. openAPI를 사용하기 위해서 다음과 같이 유저가 입력한 설문조사 결과를 넘겨야 하는데요

이렇게 코드를 짰는데 가장 큰 문제는.. 값도 들어가고 이전 페이지로 눌러도 값이 유지가 되나 설문조사를 클릭 한 뒤 그 다음 클릭을 해야만 값이 반영이 됩니다. 즉, 방금 클릭한 문항8번의 답안이 9번을 클릭할 떄서야 undefined->'1'이런 식으로 들어가더라고요. 그래서 이것 떄문에 checked 속성도 안 되고 한 박자씩 입력이 늦습니다.

뭔가 useEffect와 clickHandler 함수의 문제 같은데 왜 입력이 늦은 걸까요? 그리고 useEffect 부분의 코드를 어떻게 고치면 좋을까요?

4번까지 입력하였는데 checked 표시도 되지 않고, 1~3번은 1,4,5가 들어갔는데 콘솔도 보시면 4번째 값은 undefined라고 뜹니다.

제가 캡처화면에 표시한 2번 스크립트가 실행되는 시점에

1번의 스크립트에서 state 업데이트가 완료된다는 보장이 없습니다.

따라서 정확히 state가 변경될 때 2번 스크립트가 동작되기 위해서는

별도로 useEffect를 활용하여 로직을 작성해주셔야합니다.

  • 추가로 굳이 currQuestionNum, currUserAnswer 라는 state를 만들어서 활용하실 필요는 없어보입니다.

11/23 체크박스 렌더링? 이슈

onChange핸들러를 사용해서 체크박스를 체크할 떄마다 localStorage에 값이 잘 적용은 되는데요..! 그런데 체크를 해도 체크박스가 적용이 되지 않고, 다음 페이지를 가거나 이전 페이지로 갔다가 와야지 체크 박스가 색칠이 됩니다. 렌더링 문제 같기도 한데.. 어떻게 하면 좋을까요?

답변

  1. 맨 처음에 localStorage에 담긴 값을 임의의 state로 불러온다.
  2. localStorage를 건들지 말고, 오직 state만 활용해서 로직을 작성한다.

3-1. useEffect 를 활용해서 임의로 생성한 state가 변경될 때마다 localStorage에 저장될 수 있도록 로직을 작성한다.

3-2. input 클릭 되거나 change 될 때 이벤트에 localStorage가 변경되도록 짠 거에 + state도 같이 변경되도록


11/24 진행바 부트스트랩 출력 안됨 이슈

코드

출력결과

다음과 같이 bootstrap을 import 하고 그 태그를 사용했는데 화면에는 출력되지 않았어요 ㅠㅠ 원래대로라면 ~% 진행중과 현재페이지는 ~입니다 사이에 진행바가 출력되어야하는데.. 무슨 함수를 넣은 것도 아니고 일단 테스트용으로 60%를 입력하여 넣었는데도 출력이 되지 않네요..

무슨 문제인 걸까요..??! 감사합니다.

 

해결

index.js 에 bootstrap 부분 style 삽입함

import "bootstrap/dist/css/bootstrap.min.css";

 


11/24 마지막 결과표페이지에서 context에 저장한 값이 사라지는 이슈

다음과 같이 Context를 이용해서 userName과 gender, startDtm이 잘 출력되는데요

여기서 새로고침을 누르면

이렇게 이름과 gender가 사라집니다 ㅠㅠ 그리고 검사일시도 새로고침을 누른 시간을 갱신되는 것 같습니다

아마 새로고침을 누르면 제 생각에는 context의 초기값으로 돌아가지는 것 같습니다.

이름이 사라지지 않고, 검사일시가 갱신되지 않고 그 당시로 되려면 어떻게 해야 하나요..??!

코드입니다

result_graph page

UserInfo context.js

 

답변

새로고침할 때 날아가는 것은 당연한거라 크게 문제라고 생각하실 필요가 없습니다.

이를 방지하려면

  1. Context 내용이 바뀔 때마다 localStorage에 저장
  2. 페이지를 새로고침할 때 localStorage로부터 복원

로직을 구현하시면 되겠습니다.

 


11/24 설문조사 결과 post요청 CORS 이슈?

sol1. 설문을 받은 결과를 post하기 위해 먼저 다음과 같이 코드를 구성했습니다

context는 다음과 같습니다.

이것은 코드 입니다.

-> 그랬더니 다음과 같은 CORS 이슈가 생깁니다.

sol2. 그래서 이번에는 다음과 같이 package.json에 proxy를 추가하고 바꿔보았습니다.

-> 그랬더니 다음과 같은 404 Not Found 에러가 생깁니다

제 생각에는 proxy를 설정 안하면 www.inspct.go.kr이랑 지금 저희가 쓰는 서버랑 다르니까 보안상으로 막아놓은 것 같고 proxy를 설정하면 원래대로라면 http://프록시/inspct/openapi/test/report?.... 로 가야되는데 지금 보시면 http://localhost:3000/inspct/openapi/test/report?...으로 떠서 오류인 것 같습니다.

그리고 추가로 저 추가 에러도 뭔가 이유가 있어 보입니다 ㅠㅠ

일단 방법을 더 찾아서 해보고 있겠습니다..!

 

+++ 추가

오류 해결하였습니다! 저는 브라우저나 서버 문제일 줄 알았는데.. async await을 잘못 짜서 생긴 문제 같습니다.

그래서 궁금한 점은

왼쪽이 정상적으로 post가 이루어지는 코드고, 오른쪽이 에러는 뜨지 않았으나 response를 받지 못했던 코드입니다.

두 코드의 차이점이라고는 async함수를 두번 썼냐 아니냐의 차이밖에 없는 것 같은데

왜 async를 두번 써야지만 post가 정상적으로 이루어지는건가요??

감사합니다

 

답변

await으로 비동기 함수를 호출할 경우 .then으로 응답을 받는 게 아니고,

const res = await axios.post(...);
console.log(res)
...

와 같은 형태로 코드를 작성해주셔야 합니다.

 


11/25 그래프 Chart.js react-chart-2 에 데이터 내가 쓴 데이터로 변경하기 이슈

바 그래프를 만들기 위해 이와 같이 canvas와 chart.js,react-chartjs-2를 이용해보았는데요

다음과 같이 data:에다가 상수로만 된 배열을 넣어주면 이와 같이 잘 그래프가 그려지는데

제가 만든 state변수 graphArr를 넣으려고 하면

콘솔은 이렇게 잘 출력되는 것 같아.. 의문입니다

그리고 제 전체 코드입니다

import axios from "axios";
import React, { useState, useRef, useEffect, useContext } from "react";
import { useHistory } from "react-router-dom";
import { UserContext } from "./UserInfo";
import Chart from "chart.js/auto";
import { Bar } from "react-chartjs-2";

export default function ResultGraph() {
  const context = useContext(UserContext);
  const [graphArr, setGraphArr] = useState([]);
  // const latestGraphArr = useRef(graphArr);
  const [jobs, setJobs] = useState([]);
  const [majors, setMajors] = useState([]);
  // const [seqIndex, setSeqIndex] = useState([]);
  //CORS : Cross Origin Resource Sharing 교차 출처 리소스 공유
  // 도메인과 포트가 서로 다른 서버로 client를 요청했을 때 브라우저가 보안상 이유로 API를 차단하는 문제. ex client는 8080포트, server는 9000포트일 때.
  //나는 지금 백엔드 없이 프론트React만 사용하므로 요청받는 server에서 모든 요청을 허가한다든지 백엔드에 cors 패키지를 설치해 미들웨어로 처리한다든지 할 수 없다.
  useEffect(() => {
    async function asyncCall() {
      let seqKey = "";
      //no1 no2 알아내기 위한 변수
      let result1 = [];
      let result2 = [];
      let resultObj = [];
      let No1Index = "";
      let No2Index = "";
      let NoIndex = [];

      const uploadData = await axios
        .post(
          `http://www.career.go.kr/inspct/openapi/test/report?apikey=${context.apikey}&qestrnSeq=${context.qestrnSeq}`,
          context
        )
        .then((res) => {
          // console.log(res);
          seqKey = res.data.RESULT.url.split("seq=")[1];
          console.log(seqKey);
          return seqKey;
        })
        .catch((err) => {
          console.error(err);
        });
      const reloadData = await axios
        .get(`https://www.career.go.kr/inspct/api/psycho/report?seq=${seqKey}`)
        .then((res) => {
          console.log("get완료 res:", res);
          result1 = res.data.result.wonScore.split(" "); //['1=3', '2=3', '3=4', '4=3', '5=4', '6=5', '7=5', '8=1', '']
          result1.pop(); //마지막 하나 뺌 result1 길이 8

          // 1. setGraphArr
          const newGraphArr = [...graphArr];
          for (let i = 0; i < result1.length; i++) {
            newGraphArr.push(result1[i].split("=")[1]);
          }
          setGraphArr(newGraphArr);
          console.log("newGraphArr:", newGraphArr); //['3','3','4','4','5','5','1']

          result2 = result1.sort(function (a, b) {
            return a[2] - b[2]; //오름차순 return 1, -1, 0
          });
          console.log("result2:", result2); //['8=1', '1=3', '3=3', '5=3', '2=4', '4=4', '6=5', '7=5']
          No1Index = result2[result2.length - 1].split("=")[0]; //문항번호 가져와야되니 앞에 놈
          No2Index = result2[result2.length - 2].split("=")[0];
          NoIndex = [No1Index, No2Index];
          console.log("NoIndex:", NoIndex);

          // 2. setSeqIndex
          // const newSeqIndex = [...seqIndex];
          // newSeqIndex.push(result2[result2.length - 1].split("=")[0]);
          // newSeqIndex.push(result2[result2.length - 2].split("=")[0]);
          // console.log("newSeqIndex: ", newSeqIndex);
          // setSeqIndex(newSeqIndex);

          // return seqIndex;
        })
        .catch((err) => {
          console.error(err);
        });
      const requestJobs = await axios
        .get(
          `https://inspct.career.go.kr/inspct/api/psycho/value/jobs?no1=${NoIndex[0]}&no2=${NoIndex[1]}`
        )
        .then((res) => {
          console.log("get완료 종사자 평균학력: ", res);
          const newJobs = [...jobs];
        })
        .catch((err) => {
          console.error(err);
        });
    }
    asyncCall();
  }, []);
  //https://www.career.go.kr/inspct/web/psycho/value/report?seq=NTU3MTA5NDE

  // 3. state들 출력용 useEffect
  useEffect(() => {
    console.log(context);
    console.log("graphArr:", graphArr);
    // console.log("seqIndex:", seqIndex);
  }, [graphArr]);

  const canvasDom = useRef(null);
  useEffect(() => {
    // latestGraphArr.current = graphArr;
    const ctx = canvasDom.current.getContext("2d");
    // console.log("ctx", ctx);
    let chart = new Chart(ctx, {
      type: "bar",
      data: {
        labels: [
          "능력발휘",
          "자율성",
          "보수",
          "안정성",
          "사회적 인정",
          "사회봉사",
          "자기계발",
          "창의성",
        ],
        datasets: [
          {
            label: "직업가치관 결과",
            backgroundColor: "rgba(0, 99, 255, 0.27)",
            borderColor: "rgba(0, 99, 255, 0.72)",
            borderWidth: 1,
            hoverBackgroundColor: "rgba(0, 99, 255, 0.427)",
            hoverBorderColor: "rgba(0, 99, 255, 0.72)",
            // data: ["3", "3", "4", "3", "4", "5", "5", "1"],
            data: graphArr,
          },
        ],
      },
    });
  }, []);
  return (
    <div>
      <header>직업 가치관 검사 결과</header>
      <table>
        <thead>
          <tr>
            <th>이름</th>
            <th>성별</th>
            <th>검사 일시</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td>{context.name}</td>
            <td>{context.gender === "100323" ? "남" : "여"}</td>
            <td>{context.startDtm}</td>
          </tr>
        </tbody>
      </table>
      <table>
        <thead>직업가치관 결과</thead>
        <tbody>
          <canvas id="chart" ref={canvasDom}></canvas>
        </tbody>
      </table>
      <table>
        <thead>가치관과 관련이 높은 직업</thead>
        <tbody>
          <table>
            <thead>종사자 평균 학력별</thead>
            <tbody>
              <tr>
                <td>
                  <table>
                    <thead>
                      <tr>
                        <th>학력</th>
                        <th>직업</th>
                      </tr>
                    </thead>
                    <tbody>tbody 1 !</tbody>
                  </table>
                </td>
              </tr>
            </tbody>
          </table>
          <table>
            <thead>종사자 평균 전공별</thead>
            <tbody>
              <tr>
                <td>
                  <table>
                    <thead>
                      <tr>
                        <th>전공</th>
                        <th>직업</th>
                      </tr>
                    </thead>
                    <tbody>tbody 2 !</tbody>
                  </table>
                </td>
              </tr>
            </tbody>
          </table>
        </tbody>
      </table>
    </div>
  );
}

// var config = {
//   headers: { "Access-Control-Allow-Origin": "*" },
// };
// await axios({
//   method: "post",
//   url:
//     (`http://www.career.go.kr/inspct/openapi/test/report?apikey=${context.apikey}&qestrnSeq=${context.qestrnSeq}`,
//     config),
//   headers: {
//     "Content-Type": "application/json",
//   },
//   data: context,
// })

그래서 제 질문은 저 Chart부분에 data에 [2,3,3,2,3]처럼 임의의 값 말고

  1. 제가 썼던 변수 graphArr를 그래프에 적용되게 하고 싶으면 어떻게 해야 할까요..?
  2. 그리고 또 질문사항은 graphArr를 state를 써야할지 일반 변수를 최상위에다가 전역변수로 써야 할지도 고민입니다..!

감사합니다.

 

답변

useEffect의 deps 부분에 graphArr 을 넣어주세요.

Deps가 비어있으면 컴포넌트가 생성될 당시의 graphArr, 즉 []만 들어가게 됩니다.

 

11/26 chart data 변경 state 실패 이슈.. (+해결)

ㅠㅠ 의존성배열에 넣어봤는데도 안되더라구요..

이 에러에 대해 조금 검색하고 chart.destroy(); 도 추가해보았는데 아예 차트가 사라져버립니다..

정확히는 모르지만 state인 graphArr를 항상 최신 값으로 유지하기 위해 useRef를 사용해야 하는 걸까요?

혹시 다른 방법도 있을까요?

+++ 추가

해결하였습니다! 말씀해주신대로 의존성 배열에 state를 넣었더니 그 state가 변경될떄마다 리렌더링 되면서

그래프를 잘 출력하는 것 같습니다. 저 에러는(canvas is already...) canvas가 이미 있어서 나는 오류라서 if문으로 state에 아무 값이 없을때는 chart.destory()를 넣어주었더니 state변경될 때마다 잘 불러지는 것 같습니다.

useRef는 오히려 값을 갱신할 것이 아니라 유지해주는? 느낌인 것 같아서 사용하지 않았습니다.

감사합니다!

 


11/27 canvas 테이블 화면 크기 변동 시 크기 고정 이슈

다음과 같이 화면 크기를 줄이면 저 차트가 있는 테이블부분만 절대크기가 변해서 이상하게 출력이 됩니다 ㅠㅠ

현재 코드입니다..

ResultPage.js

import axios from "axios";
import React, { useState, useEffect, useContext } from "react";
import { Link } from "react-router-dom";
import { UserContext } from "./UserInfo";
import Chart from "chart.js/auto";
import { Button } from "react-bootstrap";

export default function ResultGraph() {
  const context = useContext(UserContext);
  const [graphArr, setGraphArr] = useState([]);
  // const latestGraphArr = useRef(graphArr);
  const [jobs, setJobs] = useState([]);
  const [majors, setMajors] = useState([]);
  const [endTime, setEndTime] = useState("");
  let jobArr = [];
  let majorArr = [];
  // const [seqIndex, setSeqIndex] = useState([]);
  //CORS : Cross Origin Resource Sharing 교차 출처 리소스 공유
  // 도메인과 포트가 서로 다른 서버로 client를 요청했을 때 브라우저가 보안상 이유로 API를 차단하는 문제. ex client는 8080포트, server는 9000포트일 때.
  //나는 지금 백엔드 없이 프론트React만 사용하므로 요청받는 server에서 모든 요청을 허가한다든지 백엔드에 cors 패키지를 설치해 미들웨어로 처리한다든지 할 수 없다.
  useEffect(() => {
    async function asyncCall() {
      let seqKey = "";
      //no1 no2 알아내기 위한 변수
      let result1 = [];
      let result2 = [];
      let No1Index = "";
      let No2Index = "";
      let NoIndex = [];

      const uploadData = await axios
        .post(
          `http://www.career.go.kr/inspct/openapi/test/report?apikey=${context.apikey}&qestrnSeq=${context.qestrnSeq}`,
          context
        )
        .then((res) => {
          console.log("1번째 post완료 응답 ", res);
          seqKey = res.data.RESULT.url.split("seq=")[1];
          console.log(seqKey);
          return seqKey;
        })
        .catch((err) => {
          console.error(err);
        });
      const reloadData = await axios
        .get(`https://www.career.go.kr/inspct/api/psycho/report?seq=${seqKey}`)
        .then((res) => {
          console.log("2번째 get완료 res:", res);
          setEndTime(res.data.result.endDtm.split("T")[0]);
          result1 = res.data.result.wonScore.split(" "); //['1=3', '2=3', '3=4', '4=3', '5=4', '6=5', '7=5', '8=1', '']
          result1.pop(); //마지막 하나 뺌 result1 길이 8

          // 1. setGraphArr
          const newGraphArr = [...graphArr];
          for (let i = 0; i < result1.length; i++) {
            newGraphArr.push(result1[i].split("=")[1]);
          }
          setGraphArr(newGraphArr);
          console.log("newGraphArr:", newGraphArr); //['3','3','4','4','5','5','1']

          result2 = result1.sort(function (a, b) {
            return a[2] - b[2]; //오름차순 return 1, -1, 0
          });
          console.log("result2:", result2); //['8=1', '1=3', '3=3', '5=3', '2=4', '4=4', '6=5', '7=5']
          No1Index = result2[result2.length - 1].split("=")[0]; //문항번호 가져와야되니 앞에 놈
          No2Index = result2[result2.length - 2].split("=")[0];
          NoIndex = [No1Index, No2Index];
          console.log("NoIndex:", NoIndex);

          // 2. setSeqIndex
          // const newSeqIndex = [...seqIndex];
          // newSeqIndex.push(result2[result2.length - 1].split("=")[0]);
          // newSeqIndex.push(result2[result2.length - 2].split("=")[0]);
          // console.log("newSeqIndex: ", newSeqIndex);
          // setSeqIndex(newSeqIndex);

          // return seqIndex;
        })
        .catch((err) => {
          console.error(err);
        });
      // 3. 종사자 평균 학력별 get
      const requestJobs = await axios
        .get(
          `https://inspct.career.go.kr/inspct/api/psycho/value/jobs?no1=${NoIndex[0]}&no2=${NoIndex[1]}`
        )
        .then((res) => {
          console.log("3번째 get완료 종사자 평균 학력별: ", res);
          //setJobs 버전
          const newJobs = [...jobs];
          for (let i = 0; i < res.data.length; i++) {
            newJobs.push(res.data[i]);
          }
          console.log("newJobs:", newJobs);
          setJobs(newJobs);
          //일반 변수 버전
          for (let i = 0; i < res.data.length; i++) {
            jobArr.push(res.data[i]);
          }
        })
        .catch((err) => {
          console.error(err);
        });
      // 4. 종사자 평균 전공별 get
      const requestMajors = await axios
        .get(
          `https://inspct.career.go.kr/inspct/api/psycho/value/majors?no1=${NoIndex[0]}&no2=${NoIndex[1]}`
        )
        .then((res) => {
          console.log("4번째 get완료 종사자 평균 전공별:", res);
          //setMajors 버전
          const newMajors = [...majors];
          for (let i = 0; i < res.data.length; i++) {
            newMajors.push(res.data[i]);
          }
          console.log("newMajors:", newMajors);
          setMajors(newMajors);
          //일반 변수 버전
          for (let i = 0; i < res.data.length; i++) {
            majorArr.push(res.data[i]);
          }
        })
        .catch((err) => console.error(err));
    }
    asyncCall();
  }, []);
  //https://www.career.go.kr/inspct/web/psycho/value/report?seq=NTU3MTA5NDE

  function printJobs() {
    console.log("printJobs start - jobs:", jobs);
    const HighJobs = [];
    const SpeciJobs = [];
    const UnivJobs = [];
    const LabJobs = [];
    for (let i = 0; i < jobs.length; i++) {
      if (jobs[i][2] <= 2) {
        HighJobs.push(jobs[i][1]); //['경찰관','레크리에이션지도자','마술사']
      } else if (jobs[i][2] === 3) {
        SpeciJobs.push(jobs[i][1]);
      } else if (jobs[i][2] === 4) {
        UnivJobs.push(jobs[i][1]);
      } else if (jobs[i][2] >= 5) {
        LabJobs.push(jobs[i][1]);
      }
    }
    const HighJobsStr = HighJobs.join(" / "); //'경찰관 / 레크리에이션지도자 / 마술사
    const SpeciJobsStr = SpeciJobs.join(" / ");
    const UnivJobsStr = UnivJobs.join(" / ");
    const LabJobsStr = LabJobs.join(" / ");
    return [HighJobsStr, SpeciJobsStr, UnivJobsStr, LabJobsStr];
  }

  function printJobs2() {
    console.log("printJobs2 start - majors:", majors);
    const Jobs0 = [];
    const Jobs1 = [];
    const Jobs2 = [];
    const Jobs3 = [];
    const Jobs4 = [];
    const Jobs5 = [];
    const Jobs6 = [];
    const Jobs7 = [];
    for (let i = 0; i < majors.length; i++) {
      if (majors[i][2] === 0) {
        Jobs0.push(majors[i][1]); //['경찰관','레크리에이션지도자','마술사']
      } else if (majors[i][2] === 1) {
        Jobs1.push(majors[i][1]);
      } else if (majors[i][2] === 2) {
        Jobs2.push(majors[i][1]);
      } else if (majors[i][2] === 3) {
        Jobs3.push(majors[i][1]);
      } else if (majors[i][2] === 4) {
        Jobs4.push(majors[i][1]);
      } else if (majors[i][2] === 5) {
        Jobs5.push(majors[i][1]);
      } else if (majors[i][2] === 6) {
        Jobs6.push(majors[i][1]);
      } else if (majors[i][2] === 7) {
        Jobs7.push(majors[i][1]);
      }
    }
    const JobsStr0 = Jobs0.join(" / "); //'경찰관 / 레크리에이션지도자 / 마술사
    const JobsStr1 = Jobs1.join(" / ");
    const JobsStr2 = Jobs2.join(" / ");
    const JobsStr3 = Jobs3.join(" / ");
    const JobsStr4 = Jobs4.join(" / ");
    const JobsStr5 = Jobs5.join(" / ");
    const JobsStr6 = Jobs6.join(" / ");
    const JobsStr7 = Jobs7.join(" / ");
    return [
      JobsStr0,
      JobsStr1,
      JobsStr2,
      JobsStr3,
      JobsStr4,
      JobsStr5,
      JobsStr6,
      JobsStr7,
    ];
  }

  // 5. state들 출력용 useEffect
  useEffect(() => {
    console.log("-------state changed----------");
    console.log(context);
    console.log("graphArr:", graphArr);
    console.log("jobs:", jobs);
    console.log("majors:", majors);
    console.log("-------state changed----------");
    // console.log("seqIndex:", seqIndex);
  }, [graphArr, jobs, majors]);

  useEffect(() => {
    let ctx = document.getElementById("Mychart").getContext("2d");
    console.log("Now Draw graph : graphArr", graphArr);
    // if (typeof Mychart !== "undefined") Mychart.destroy();
    let Mychart = new Chart(ctx, {
      type: "bar",
      data: {
        labels: [
          "능력발휘",
          "자율성",
          "보수",
          "안정성",
          "사회적 인정",
          "사회봉사",
          "자기계발",
          "창의성",
        ],
        datasets: [
          {
            label: "직업가치관 결과",
            backgroundColor: "rgba(0, 99, 255, 0.27)",
            borderColor: "rgba(0, 99, 255, 0.72)",
            borderWidth: 1,
            hoverBackgroundColor: "rgba(0, 99, 255, 0.427)",
            hoverBorderColor: "rgba(0, 99, 255, 0.72)",
            // data: ["3", "3", "4", "3", "4", "5", "5", "1"],
            data: graphArr,
          },
        ],
      },
    });
    if (graphArr.length === 0) Mychart.destroy();
  }, [graphArr]);
  return (
    <div>
      <header>직업 가치관 검사 결과</header>
      <table>
        <thead>
          <tr>
            <th>이름</th>
            <th>성별</th>
            <th>검사 일시</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td>{context.name}</td>
            <td>{context.gender === "100323" ? "남" : "여"}</td>
            <td>{endTime}</td>
          </tr>
        </tbody>
      </table>
      <table>
        <thead>직업가치관 결과</thead>
        <tbody>
          <canvas id="Mychart"></canvas>
        </tbody>
      </table>
      <table>
        <thead>가치관과 관련이 높은 직업</thead>
        <tbody></tbody>
      </table>
      <table>
        <thead>1. 종사자 평균 학력별</thead>
      </table>
      <table>
        <thead>
          <tr>
            <th>학력</th>
            <th>직업</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td>고등학교 졸업자 </td>
            <td>{printJobs()[0]}</td>
          </tr>
          <tr>
            <td>전문대학교 졸업자 </td>
            <td>{printJobs()[1]}</td>
          </tr>
          <tr>
            <td>대학교 졸업자 </td>
            <td>{printJobs()[2]}</td>
          </tr>
          <tr>
            <td>대학원 졸업자 </td>
            <td>{printJobs()[3]}</td>
          </tr>
        </tbody>
      </table>
      <div>
        <br />
      </div>
      <table>
        <thead>2. 종사자 평균 전공별</thead>
      </table>
      <table>
        <thead>
          <tr>
            <th>전공</th>
            <th>직업</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td>계열무관</td>
            <td>{printJobs2()[0]}</td>
          </tr>
          <tr>
            <td>인문</td>
            <td>{printJobs2()[1]}</td>
          </tr>
          <tr>
            <td>사회</td>
            <td>{printJobs2()[2]}</td>
          </tr>
          <tr>
            <td>교육</td>
            <td>{printJobs2()[3]}</td>
          </tr>
          <tr>
            <td>공학</td>
            <td>{printJobs2()[4]}</td>
          </tr>
          <tr>
            <td>자연</td>
            <td>{printJobs2()[5]}</td>
          </tr>
          <tr>
            <td>의학</td>
            <td>{printJobs2()[6]}</td>
          </tr>
          <tr>
            <td>예체능</td>
            <td>{printJobs2()[7]}</td>
          </tr>
        </tbody>
      </table>
      <div>
        <br />
      </div>
      <Link to="/">
        <Button variant="primary" size="lg">
          다시 검사하기
        </Button>
      </Link>
    </div>
  );
}

// var config = {
//   headers: { "Access-Control-Allow-Origin": "*" },
// };
// await axios({
//   method: "post",
//   url:
//     (`http://www.career.go.kr/inspct/openapi/test/report?apikey=${context.apikey}&qestrnSeq=${context.qestrnSeq}`,
//     config),
//   headers: {
//     "Content-Type": "application/json",
//   },
//   data: context,
// })

index.css

@import url("https://fonts.googleapis.com/css2?family=Barlow:ital,wght@0,400;1,600&family=IBM+Plex+Sans+KR&family=Nunito:wght@600&display=swap");
body {
  margin: 0;
  /* font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
    "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
    sans-serif; */
  font-family: "Barlow", "IBM Plex Sans KR", "Nunito", sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}
.testResultPage {
  padding: 300px 0px;
}

.startPage {
  padding: 300px 0px;
}

.startPage header {
  background-color: rgb(157, 196, 255);
  font-family: "Barlow", "IBM Plex Sans KR", "Nunito", sans-serif;
}
.startPage body {
  font-family: "Barlow", "IBM Plex Sans KR", "Nunito", sans-serif;
  padding: 15px 0px;
}
.testExamplePage {
  font-family: "Barlow", "IBM Plex Sans KR", "Nunito", sans-serif;
  padding: 100px 0px;
}
.testExamplePage form div p {
  font-family: "Barlow", "IBM Plex Sans KR", "Nunito", sans-serif;
  margin: 0px 0px 5px;
}
.testPage {
  font-family: "Barlow", "IBM Plex Sans KR", "Nunito", sans-serif;
  padding: 0px 0px 10px 0px;
  margin: 0px 0px 10px 0px;
}

.testPage div.Progress {
  margin: 10px 0px;
}
div {
  font-family: "Barlow", "IBM Plex Sans KR", "Nunito", sans-serif;
}
.testPage p {
  font-family: "Barlow", "IBM Plex Sans KR", "Nunito", sans-serif;
  margin: 0px 0px 5px;
}
p {
  font-family: "Barlow", "IBM Plex Sans KR", "Nunito", sans-serif;
  font-size: 18px;
}
h2 {
  font-family: "Barlow", "IBM Plex Sans KR", "Nunito", sans-serif;
  font-weight: bold;
}

h3 {
  font-family: "Barlow", "IBM Plex Sans KR", "Nunito", sans-serif;
}
h4 {
  font-family: "Barlow", "IBM Plex Sans KR", "Nunito", sans-serif;
}
/* code {
  font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
    monospace;
} */

code {
  font-family: "Barlow", "IBM Plex Sans KR", "Nunito", sans-serif;
}

header {
  font-family: "Barlow", "IBM Plex Sans KR", "Nunito", sans-serif;
  font-weight: bold;
  font-size: 30px;
  color: rgb(0, 25, 126);
}

/* button {
  padding: 8px;
  font-weight: bold;
  font-size: 12px;
  cursor: pointer;
  border: 0 none;
  border-radius: 6px;
  padding: 9px 14px;
  color: #fff;
  background-color: dodgerblue;
} */

table {
  border-collapse: collapse;
  width: 60%;
  margin-left: auto;
  margin-right: auto;
}

table thead {
  font-size: 20px;
  font-weight: bold;
}
table td {
  width: 25%;
  height: 70px;
  border: 1px solid #ccc;
  text-align: center;
  font-size: 20px;
}

/* a {
  text-decoration: none;
  display: block;
  padding: 20px 0;
  font-weight: bold;
  color: #fff;
  text-align: center;
  border-radius: 10px;
  background-color: rgb(16, 60, 104);
} */

table tbody canvas,
table tbody canvas::before,
table tbody canvas::after {
  box-sizing: border-box;
  width: 100%;
}

이게 index.css 건드리기 전까지는 절대크기로 창을 줄여도 잘 작아졌던 거 같은데 어느순간 이상해진 것 같습니다..

증상은 윈도우 창 크기를 크게 만들면 커지기는 하는데, 줄어드는 건 안되더라구요..

일단 progress bar bootstrap을 불렀을떄까지는 잘 줄어들었는데,

그 이후로 변동사항은 bootstrap에서 버튼을 불러오고, 저런 식으로 index.css를 바꾼 것밖에 없는 것 같습니다 ㅠㅠ

윈도우 창을 줄이면 해당 canvas내 그래프도 작아지게 하려면 어떻게 해야 할까요 ㅠㅠ

 

해결

styled된 div로 한번 더 감싸고 auto ratio 등을 지정해서 윈도우 창 크기에 따라 잘 줄어드는 것 같습니다

const Container = styled.div`
  width: 60%;
  background-color: white;
  margin-left: auto;
  margin-right: auto;
  aspect-ratio: auto;
`;

return 부분
<Container>
        <canvas id="Mychart"></canvas>
</Container>