[TIL] 220210-11

먼지·2022년 2월 11일
0

TIL

목록 보기
21/57
post-thumbnail

헬프
뮤테이션은 버튼 누르면 작동하는 게 아니고,
get 요청 말고 데이터를 수정하는 create, update, delete 할 때 쓰이는 것임.
검색은 서버에 있는 데이터가 수정되는 게 아니니까 뮤테이션을 쓰면 안 되고 쿼리스트링에 있는 검색어를 캐시키 배열에 추가해야 함.

올바른 URL 설계 : 1) Query string과 Path Variable 이해하기

취향차이

board/qna
board/qna?page=1

     <List>
        <Item>
          <SLink
            to={{
              pathname: '/board/qna',
              state: { type: 'qna', sort: 'id' },
              search: '?page=1',
            }}
          >
            <Icon>
              <AiOutlineQuestion />
            </Icon>
            <Text>Q&amp;A</Text>
          </SLink>
        </Item>
        <Item>

controlled 컴포넌트

select에 value를 적지 않아서 동기화가 되지 않음. input의 값을 바꿨을 때는 상태가 바뀌었지만 상태를 바꿨을 때는 input의 value가 바뀌지 않았음.

<SearchSelect
  value={searchModeInput}
  onChange={(e) => setSearchModeInput(e.target.value)}
>
  <option value="title" defaultValue>
    제목
  </option>
  <option value="content">내용</option>
  <option value="all">제목+내용</option>
</SearchSelect>

useQuery에 조건문을 사용할 수 있었다..! 유즈쿼리 안이 복잡해지지 않게 하려면 함수를 빼는 것도 좋음.

const { isLoading, data } = useQuery( [boardType + 'Board', curPage, sort, searchInfo], () =>{
  if (searchInfo === ''){
     return boardApi.getPosts(curPage, sort, boardType).then((res) => res.data)
  } else {
    return boardApi .searchPosts(keyword, curPage, searchMode, boardType) .then((res) => res.data),
  }
});

돔에서 가져온 innerText는 문자열이다~

useState,,, 상태 렌더링

어제 코드

import React, { useEffect, useState } from 'react';
import { Link, useLocation, useHistory } from 'react-router-dom';
import styled from 'styled-components';
import { AiOutlineSearch } from 'react-icons/ai';
import { boardApi } from '../api';
import { useUser } from '../context';
import { useQuery } from 'react-query';
import Post from '../components/Post';
import PageList from '../components/PageList';

const Board = () => {
  const user = useUser();
  const location = useLocation();
  const boardType = location.pathname.split('/')[2] || location.state?.type;
  const boardTitle =
    (boardType === 'qna' && 'Q&A') ||
    (boardType === 'tech' && 'Tech') ||
    (boardType === 'free' && '자유게시판');
  // const sort = location.pathname.split('=')[1] || location.state?.sort || null;

  const history = useHistory();

  const [sort, setSort] = useState('createdDate');
  const [curPage, setCurPage] = useState(1);
  const [pageList, setPageList] = useState([1, 2, 3, 4, 5]);

  const [searchModeInput, setSearchModeInput] = useState('title'); // searchModeInput
  const [keywordInput, setKeywordInput] = useState('');
  const [searchInfo, setSearchInfo] = useState({
    keyword: '',
    searchMode: '',
  });
  const { keyword, searchMode } = searchInfo;

  // 검색 결과 이후에 cancle 버튼을 누르거나, 최신순 조회순 등 탭, 다른 boardType을 클릭해도
  // http://localhost:3000/board/qna?searchMode=undefined&keyword=undefined
  // url이 저렇게 뜸. useEffect를 어떻게 섞어쓸 수 있을까
  const { isLoading, data } = useQuery(
    [`${boardType}Board`, curPage, sort, searchInfo],
    () => {
      if (!keyword || !searchMode) {
        return boardApi
          .getPosts(curPage, sort, boardType)
          .then((res) => res.data);
      } else {
        return boardApi
          .searchPosts(keyword, curPage, searchMode, boardType)
          .then((res) => res.data);
      }
    }
  );

  console.log('Board data', data, curPage, sort);

  const handleSearch = (e) => {
    e.preventDefault();
    console.log('handleSearch');
    if (keywordInput.trim().length < 2) {
      alert('검색어는 2글자 이상 입력해주세요.');
      return;
    }
    setSearchInfo({
      ...searchInfo,
      keyword: keywordInput,
      searchMode: searchModeInput,
    });
  };

  useEffect(() => {
    if (!keyword || !searchMode) return;
    const searchParams = new URLSearchParams();
    searchParams.set('searchMode', searchMode);
    searchParams.set('keyword', keyword);
    console.log('searchParams', searchParams.toString());
    setSort('createdDate');
    history.push(`/board/${boardType}?` + searchParams.toString());
  }, [boardType, history, keyword, searchMode]);

  // 1. initSearchInfo Constant 만들기
  // 2. 생각이 기계적이다.
  const handleOrderListClick = (e) => {
    // 자의적으로 변하는 화면, 이벤트, ui에 의존하는 코드는 좋지 않음.
    // 돔에 직접 접근하는 것은 좋지 않음! 바뀔 수 있음
    // 기획자와 카피라이터
    const sort = e.target.dataset.name;

    // 함수로 쪼개기
    // 중복이 있어서 쪼개는 게 아니라 -> 비슷한 역할을 가진 것들을 묶기
    // 서로 다른 로직이 섞이지 않게
    // Board를 만드는데 if문이 3개임 .필요한 로직중에 하나지만
    // inntertText에 따라서 sort를 뽑아주는 로직.

    // 언어에따라

    setCurPage(1);
    setSort(sort);
    setSearchInfo({
      keyword: '',
      searchMode: '',
    });
    history.push(`/board/${boardType}`);
  };

  const handleCancelSearch = (e) => {
    e.preventDefault();
    setKeywordInput('');
    setSearchInfo({
      keyword: '',
      searchMode: '',
    });
    history.push(`/board/${boardType}`);
    // http://localhost:3000/board/qna?searchMode=undefined&keyword=undefined
  };

  return (
    <Container>
      <Header>
        <Title>{boardTitle}</Title>
        {user && (
          <Button>
            <Link
              className="link"
              to={{ pathname: '/write', state: { type: boardType } }}
            >
              새 글 쓰기
            </Link>
          </Button>
        )}
      </Header>

      <FilterContainer>
        <OrderList onClick={handleOrderListClick}>
          {[
            ['최신순', 'createdDate'],
            ['조회순', 'views'],
            ['댓글순', 'commentSize'],
            ['좋아요순', 'likes'],
          ].map(([text, name]) => (
            <OrderItem key={name} data-name={name} active={sort === name}>
              {text}
            </OrderItem>
          ))}
        </OrderList>

        <SearchForm onSubmit={handleSearch}>
          <SearchSelect onChange={(e) => setSearchModeInput(e.target.value)}>
            <option value="title" defaultValue>
              제목
            </option>
            <option value="content">내용</option>
            <option value="all">제목+내용</option>
          </SearchSelect>
          <SearchInput
            value={keywordInput}
            onChange={(e) => setKeywordInput(e.target.value)}
            minLength="2"
            maxLength="20"
          />
          {/* 폼안에 버튼 만들 때 조심하기 */}
          <button type="button" className="dbtn" onClick={handleCancelSearch}>
            x
          </button>
          <SearchButton type="submit">
            <AiOutlineSearch className="sbtn" />
          </SearchButton>
        </SearchForm>
      </FilterContainer>

      {!isLoading ? (
        <>
          {data?.contents?.map((post) => (
            <Post
              key={post.id}
              post={post}
              type={boardType}
              fci={data?.contents[0].id}
            />
          ))}
        </>
      ) : (
        <div>loading..</div>
      )}
    </Container>
  );
};

export default Board;

// 오늘코드

import React, { useEffect, useState } from 'react';
import { Link, useLocation, useHistory } from 'react-router-dom';
import styled from 'styled-components';
import { AiOutlineSearch } from 'react-icons/ai';
import { MdCancel } from 'react-icons/md';
import { boardApi } from '../api';
import { useUser } from '../context';
import { useQuery } from 'react-query';
import Post from '../components/Post';
import PageList from '../components/PageList';

const Board = () => {
  const user = useUser();
  const location = useLocation();
  const boardType = location.pathname.split('/')[2] || location.state?.type;
  const boardTitle =
    (boardType === 'qna' && 'Q&A') ||
    (boardType === 'tech' && 'Tech') ||
    (boardType === 'free' && '자유게시판');

  const history = useHistory();

  const [sort, setSort] = useState('createdDate');

  const search = new URLSearchParams(location.search);
  const curPage = Number(search.get('page')) || 1;

  const [pageList, setPageList] = useState([1, 2, 3, 4, 5]);

  const [searchModeInput, setSearchModeInput] = useState('title'); // searchModeInput
  const [keywordInput, setKeywordInput] = useState('');

  const initSearchInfo = {
    searchMode: 'title',
    keyword: '',
  };
  const [searchInfo, setSearchInfo] = useState(initSearchInfo);
  const { keyword, searchMode } = searchInfo;

  // 얘가 setCurrentPage
  const fetchBoards = (page = 1) => {
    // 현재 url의 searchParams
    const searchParams = new URLSearchParams(location.search);
    searchParams.set('page', page);
    console.log('searchParams', searchParams.toString());
    history.push(`/board/${boardType}?` + searchParams.toString());
  };

  // 상태랑 아무 상관도 없음.
  const { isLoading, data } = useQuery(
    [`${boardType}Board`, curPage, sort, searchInfo],
    async () => {
      if (!keyword) {
        return boardApi
          .getPosts(curPage, sort, boardType)
          .then((res) => res.data);
      } else {
        return boardApi
          .searchPosts(keyword, curPage, searchMode, boardType)
          .then((res) => res.data);
      }
    }
  );

  const handleSearch = (e) => {
    e.preventDefault();
    fetchBoards(1);
    if (keywordInput.trim().length < 2) {
      alert('검색어는 2글자 이상 입력해주세요.');
      return;
    }
    setSearchInfo({
      searchMode: searchModeInput,
      keyword: keywordInput,
    });
  };

  useEffect(() => {
    if (!keyword || !searchMode) return;
    const searchParams = new URLSearchParams(location.search);
    searchParams.set('searchMode', searchMode);
    searchParams.set('keyword', keyword);
    console.log('searchParams', searchParams.toString());
    setSort('createdDate');
    history.push(`/board/${boardType}?` + searchParams.toString());
  }, [boardType, history, keyword, searchMode]);

  const handleOrderListClick = (e) => {
    fetchBoards(1);
    const sort = e.target.dataset.name;

    setSort(sort);

    setSearchModeInput('title'); // 작동x
    setKeywordInput('');

    setSearchInfo(initSearchInfo);
    history.push(`/board/${boardType}`);
  };

  const handleCancelSearch = () => {
    fetchBoards(1);

    setSearchModeInput('title'); // 작동x
    setKeywordInput('');

    setSearchInfo(initSearchInfo);
    history.push(`/board/${boardType}`);
  };

  return (
    <Container>
      <Header>
        <Title>{boardTitle}</Title>
        {user && (
          <Button>
            <Link
              className="link"
              to={{ pathname: '/write', state: { type: boardType } }}
            >
              새 글 쓰기
            </Link>
          </Button>
        )}
      </Header>

      <FilterContainer>
        <OrderList onClick={handleOrderListClick}>
          {[
            ['최신순', 'createdDate'],
            ['조회순', 'views'],
            ['댓글순', 'commentSize'],
            ['좋아요순', 'likes'],
          ].map(([text, name]) => (
            <OrderItem key={name} data-name={name} active={sort === name}>
              {text}
            </OrderItem>
          ))}
        </OrderList>

        <SearchForm onSubmit={handleSearch}>
          <SearchSelect
            value={searchModeInput}
            onChange={(e) => setSearchModeInput(e.target.value)}
          >
            <option value="title" defaultValue>
              제목
            </option>
            <option value="content">내용</option>
            <option value="all">제목+내용</option>
          </SearchSelect>
          <SearchInput
            value={keywordInput}
            onChange={(e) => setKeywordInput(e.target.value)}
            minLength="2"
            maxLength="20"
          />
          <SearchButton type="submit">
            <AiOutlineSearch className="sbtn" />
          </SearchButton>
          {/* 폼안에 버튼 만들 때 조심하기 */}
          {keyword !== '' && (
            <CancelButton type="button" onClick={handleCancelSearch}>
              <div className="cbtn">
                <MdCancel /> clear
              </div>
            </CancelButton>
          )}
        </SearchForm>
      </FilterContainer>

      {!isLoading ? (
        <>
          {data?.contents?.map((post) => (
            <Post
              key={post.id}
              post={post}
              type={boardType}
              fci={data?.contents[0].id}
            />
          ))}
          {data?.totalElements > 0 ? (
            <PageList
              page={pageList}
              setPage={setPageList}
              fetchContents={fetchBoards}
              totalPages={data?.totalPages}
              // typeof curPage - numbers
              currentPage={data?.currentPage}
              sort={sort}
            />
          ) : (
            <div style={{ marginTop: 150, textAlign: 'center' }}>
              아직 게시물이 없습니다.
            </div>
          )}
        </>
      ) : (
        <div>loading..</div>
      )}
    </Container>
  );
};

export default Board;

profile
꾸준히 자유롭게 즐겁게

0개의 댓글