word에서 생성한 단어들을 game에 적용하기

개발공부·2023년 1월 3일
0

* 결과 (이전 체크박스 내용)

1) 개별 체크박스 적용(word -> game)

2) 전체 체크박스 적용(word -> game)

3) 개별 체크박스 적용(game -> word)

4) 전체 체크박스 적용(game -> word)

* 만드는 데 중점을 둔 것

status가 "C" : 체크박스가 checked된 것, checkedWordList에 단어가 들어가 있는 것
status가 "A" : 체크박스가 checked 해제된 것, checkedWordList에 단어가 없음

  • word에서 사용한 코드(axios 관련 체크박스)를 재사용 하는 것
    ▶ 체크박스 클릭 시 총 추가된 단어(checkedWordList)에 적용되도록 단어 정보들이 들어있어야 함
    ▶ 개별 단어 체크박스 클릭 후 새로운 단어
  • game의 모달에서 취소 버튼('단어로 이동'으로 수정)은 "/" 주소로 이동하게 함
  • 기존에 체크박스를 만들기 전 체크박스 여부를 확인(loadCheckedRequest)
  • word에서 적용한 체크박스가 game에서도 동일하게 적용되고 반대도 마찬가지

* 코드 관련

1) loadWordsRequest(단어 목록 불러오기), loadCheckedRequest(체크된 여부 불러오기)

[components/game/StartModal.js]

import {
  loadWordsRequest,
  loadCheckedRequest,
  changeStatusWordAllRequest,
} from "../../redux/feature/wordSlice";

  useEffect(() => {
    dispatch(loadWordsRequest());
    dispatch(loadCheckedRequest());
  }, []);
  
  const onChangeAllSelected = useCallback((e) => {
    const checkboxClicked = e.target;
    const userId = e.target.value;

    if (checkboxClicked.checked) {
      dispatch(changeStatusWordAllRequest({ status: "C", userId: userId }));
    } else if (!checkboxClicked.checked) {
      dispatch(changeStatusWordAllRequest({ status: "A", userId: userId }));
    }
  }, []);
  
  return (
  //전체 체크박스
  <div className="flex">
                            <p className="w-1/2">
                              총 추가된 단어 :
                              <span className="font-bold text-red-500 ml-3">
                                {checkedWordList.length}
                              </span>
                            </p>
                            <div className="w-1/2 bg-gray-100">
                              <input
                                checked={showStatus > 0 ? true : false}
                                onChange={onChangeAllSelected}
                                value={UserId}
                                name="checkItem"
                                type="checkbox"
                                className="mr-1 h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500"
                              />
                              <span className="font-bold">
                                전체 선택 / 해제
                              </span>
                            </div>
                          </div>
  
  //개별 단어 체크박스
   <div className="group justify-center flex overflow-y-auto max-h-96 overflow-hidden rounded-md">
                          <div>
                            {wordLists.map((word, index) => { //단어 목록들
                              {
                                return (
                                  <StartWordList word={word} index={index} />
                                );
                              }
                            })}
                          </div>
                        </div>
  
  )

[components/game/StartWordList.js]

import React, { useCallback, useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { changeStatusWordRequest } from "../../redux/feature/wordSlice";

const StartWordList = ({ word, index }) => {
  const dispatch = useDispatch();
  const [bChecked, setBChecked] = useState(false);
  const { checkedWordList } = useSelector((state) => state.word);

  useEffect(() => {
    if (word.status === "C") {
      setBChecked(true);
    } else if (word.status === "A") {
      setBChecked(false);
    }
  }, [word.status]);

  const onChangeSelected = useCallback((e) => {
    const checkboxClicked = e.target;
    const wordIndex = e.target.value;

    if (checkboxClicked.checked) {
      setBChecked(true);
      dispatch(changeStatusWordRequest({ id: wordIndex, status: "C" }));
    } else if (!checkboxClicked.checked) {
      setBChecked(false);
      dispatch(changeStatusWordRequest({ id: wordIndex, status: "A" }));
    }
  }, []);

  return (
    <div className="m-1 flex border-2 border-light-green h-12 w-80 rounded-md">
      <input
        checked={bChecked}
        onChange={onChangeSelected}
        value={word.id}
        name="checkItem"
        type="checkbox"
        className="mt-3 ml-2 h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500"
      />
      <li className="my-2.5 flex">
        <div className="flex ml-2">
          <p className="text-sm font-medium text-slate-900  w-32 truncate">
            ({index}) {word.english} ({word.status})
          </p>
          <p className="ml-3 text-sm font-medium text-slate-900 w-32 truncate">
            뜻: {word.korean}
          </p>
        </div>
      </li>
    </div>
  );
};

export default StartWordList;

[sagas/wordSaga.js]

function loadWordsAPI() {
  return axios.get("/words");
}

function* loadWords(action) {
  try {
    const data = action.payload;
    const result = yield call(loadWordsAPI, data);
    yield put(loadWordsSuccess(result.data));
  } catch (error) {
    yield put(loadWordsFailure(error));
    console.log(error);
  }
}

//loadChecked
function loadCheckedAPI() {
  return axios.get("/words/checked");
}

function* loadChecked(action) {
  try {
    const data = action.payload;
    const result = yield call(loadCheckedAPI, data);
    yield put(loadCheckedSuccess(result.data));
  } catch (error) {
    yield put(loadCheckedFailure(error));
    console.log(error);
  }
}

[feature/wordSlice.js] - redux-toolkit

    loadWordsSuccess: (state, action) => {
      const data = action.payload;
      state.loadWordsLoading = false;
      state.loadWordsComplete = true;
      state.wordLists.length = 0;
      state.wordLists = state.wordLists.concat(data);
    },
    
     loadCheckedSuccess: (state, action) => {
      const data = action.payload;
      state.loadCheckedLoading = false;
      state.loadCheckedComplete = true;
      state.checkedWordList.length = 0; //초기화
      state.checkedWordList = state.checkedWordList.concat(data);
    },

[backend/routes/words.js]

const express = require("express");
const { Op } = require("sequelize");
const { User, Word } = require("../models");

const router = express.Router();

//전체 단어들
router.get("/", async (req, res, next) => {
  try {
    const where = {};
    if (parseInt(req.query.lastId, 10)) {
      // 초기 로딩이 아닐 때
      where.id = { [Op.lt]: parseInt(req.query.lastId, 10) };
    } // 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1

    const words = await Word.findAll({
      //모든 게시글 가져옴
      where,
      limit: 10,
      order: [
        ["createdAt", "DESC"], //최신 게시글부터
      ],
      include: [
        {
          model: User,
          attributes: ["id", "nickname"],
        },
      ],
    });
    console.log(words);
    res.status(200).json(words);
  } catch (error) {
    console.error(error);
    next(error);
  }
});

router.get("/checked", async (req, res, next) => {
  try {
    const where = { status: "C", UserId: req.user.id };
    if (parseInt(req.query.lastId, 10)) {
      // 초기 로딩이 아닐 때
      where.id = { [Op.lt]: parseInt(req.query.lastId, 10) };
    } // 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1

    const words = await Word.findAll({
      //모든 게시글 가져옴
      where,
      limit: 10,
      order: [
        ["createdAt", "DESC"], //최신 게시글부터
      ],
    });
    console.log(words);
    res.status(200).json(words);
  } catch (error) {
    console.error(error);
    next(error);
  }
});

module.exports = router;

2) 체크박스 관련

[sagas/wordSaga.js]

function changeStatusAPI(data) {
  return axios.patch(`/word/${data.id}/${data.status}`, {
    id: data.id,
    status: data.status,
  });
}

function* changeStatus(action) {
  try {
    const data = action.payload;
    const result = yield call(changeStatusAPI, data);
    yield put(changeStatusWordSuccess(result.data));
  } catch (error) {
    yield put(changeStatusWordError(error));
    console.log(error);
  }
}

function changeStatusAllAPI(data) {
  return axios.patch(`/word/all/${data.userId}/${data.status}`, {
    status: data.status,
    userId: data.userId,
  });
}

function* changeStatusAll(action) {
  try {
    const data = action.payload;
    const result = yield call(changeStatusAllAPI, data);
    yield put(changeStatusWordAllSuccess(result.data));
  } catch (error) {
    yield put(changeStatusWordAllError(error));
    console.log(error);
  }
}

[feature/wordSlice.js] - redux-toolkit

//개별 체크박스
changeStatusWordSuccess: (state, action) => {
      const data = action.payload;
      state.changeStatusWordLoading = false;
      state.changeStatusWordComplete = true;

      const changeStatus = state.wordLists.find((v) => v.id === data.id);
      
      //이미 status가 "C"인 경우
      const showStatus = state.wordLists.find(
        (v) => v.id === data.id && v.status === "C" 
      );

      if (changeStatus) {
        changeStatus.status = data.status;
        state.checkedWordList = state.checkedWordList.concat(data);
      }
      if (showStatus) {
        //C -> A로 바꾸는 경우
        const index = state.checkedWordList.findIndex(
          (word) => word.id === data.id
        );
        state.checkedWordList.splice(index, 1); //인덱스 값 확인 후 제거
        state.checkedWordList = state.checkedWordList.filter(
          (word) => word.id !== data.id //제거된 값만 제외한 나머지
        );
      }
    },
//전체 체크박스
    changeStatusWordAllSuccess: (state, action) => {
      const data = action.payload;
      state.changeStatusWordLoading = false;
      state.changeStatusWordComplete = true;

      const changeStatus = state.wordLists.find((v) => v.UserId === data[0].UserId);
      const showStatus = state.wordLists.find(
        (v) => v.UserId === data[0].UserId && v.status === "C"
      );

      if (changeStatus) {
        state.wordLists.map((word) => {
          word.status = data[0].status;
          state.checkedWordList.length = 0; //초기화
          state.checkedWordList = state.checkedWordList.concat(data);
        });
      }
      if (showStatus) {
        state.checkedWordList.length = 0; //초기화
      }
    },

[backend/routes/word.js]

router.patch("/:id/:status", isLoggedIn, async (req, res, next) => {
  // PATCH /word/10/status
  try {
    await Word.update(
      {
        status: req.params.status,
      },
      {
        where: {
          id: req.params.id,
          UserId: req.user.id, //작성자가 본인이 맞는지?
        },
      }
    );
    const fullWord = await Word.findOne({
      where: { id: req.params.id },
    });
    res.status(201).json(fullWord); //단어 정보 가져오기
  } catch (error) {
    console.error(error);
    next(error);
  }
});

router.patch("/all/:userId/:status", isLoggedIn, async (req, res, next) => {
  // (전체 수정) PATCH /word/status/1(userId)
  try {
    await Word.update(
      {
        status: req.params.status,
      },
      {
        where: {
          [Op.or]: [{ id: { [Op.gt]: 0 } }, { id: req.params.userId }],
          UserId: req.user.id,
        },
      }
    );
    const fullWord = await Word.findAll({
      where: { status: req.params.status },
    });
    res.status(200).json(fullWord); //전체 단어 정보 가져오기
  } catch (error) {
    console.error(error);
    next(error);
  }
});
profile
개발 블로그, 티스토리(https://ba-gotocode131.tistory.com/)로 갈아탐

0개의 댓글