인프런 커뮤니티 질문&답변

재원허님의 프로필 이미지

작성한 질문수

[리뉴얼] React로 NodeBird SNS 만들기

swr 사용해보기

mutate 질문입니다!

작성

·

328

0

import React, { useEffect, useState, useCallback } from 'react';
import Head from 'next/head';
import { useSelector } from 'react-redux';
import Router from 'next/router';
import { END } from 'redux-saga';
import axios from 'axios';
import useSWR from 'swr';
 
import AppLayout from '../components/AppLayout';
import NicknameEditForm from '../components/NicknameEditForm';
import FollowList from '../components/FollowList';
import { LOAD_MY_INFO_REQUEST } from '../reducers/user';
import wrapper from '../store/configureStore';
import { backUrl } from '../config/config';

const fetcher = (url) => axios.get(url, { withCredentials: true }).then(result => result.data);

const Profile = () => {
  const { me } = useSelector((state) => state.user);
  const [followersLimit, setFollowersLimit] = useState(3);
  const [followingsLimit, setFollowingsLimit] = useState(3);

  // 팔로워, 팔로잉 불러오기
  const { data: followersData, error: followerError, mutate: mutateFollower } = useSWR(`${backUrl}/user/followers?limit=${followersLimit}`, fetcher);
  const { data: followingsData, error: followingError, mutate: mutateFollowing } = useSWR(`${backUrl}/user/followings?limit=${followingsLimit}`, fetcher);

  useEffect(() => {
    if (!(me && me.id)) {
      Router.replace('/');
    }
  }, [me && me.id]);

  const loadMoreFollowings = useCallback(() => {
    setFollowingsLimit((prev) => prev + 3);
  }, []);

  const loadMoreFollowers = useCallback(() => {
    setFollowersLimit((prev) => prev + 3);
  }, []);

  if (!me) {
    return '내 정보 로딩중...';
  }

  // 주의: return이 hooks보다 위에 있으면 안됨
  if (followerError || followingError) {
    console.error(followerError, followingError);
    return <div>팔로잉/팔로워 로딩 중 에러가 발생합니다.</div>
  }

  return (
    <>
     <Head>
       <title>내 프로필 | NodeBird</title>
     </Head>
      <AppLayout>
        <NicknameEditForm />
        <FollowList
          header="팔로잉"
          data={followingsData}
          onClickMore={loadMoreFollowings}
          loading={!followingsData && !followingError}
          mutateFollowing={mutateFollowing}
        />
        <FollowList
          header="팔로워"
          data={followersData}
          onClickMore={loadMoreFollowers}
          loading={!followersData && !followerError}
          mutateFollower={mutateFollower}
        />
      </AppLayout>
    </>
  );
};

export const getServerSideProps = wrapper.getServerSideProps(store => async ({ req }) => {
  console.log('getServerSideProps start');
  const cookie = req ? req.headers.cookie : '';
  axios.defaults.headers.Cookie = '';
  if (req && cookie) {
    axios.defaults.headers.Cookie = cookie;
  }
  store.dispatch({
    type: LOAD_MY_INFO_REQUEST,
  });
  store.dispatch(END);
  console.log('getServerSideProps end');
  await store.sagaTask.toPromise();
});

export default Profile;

 

import React from 'react';
import propTypes from 'prop-types';
import { Button, List, Card } from 'antd';
import { StopOutlined } from '@ant-design/icons';
import { useDispatch } from 'react-redux';

import { UNFOLLOW_REQUEST, REMOVE_FOLLOWER_REQUEST } from '../reducers/user';

const FollowList = ({ header, data, onClickMore, loading, mutateFollowing, mutateFollower }) => {
  const dispatch = useDispatch();
  const onCancel = (id) => () => {
    if (header === '팔로잉') {
      dispatch({
        type: UNFOLLOW_REQUEST,
        data: id,
      });
      setTimeout(() => {
        mutateFollowing((prev) => prev.filter((data) => data.id !== id));
      }, 500);
    } else {
      dispatch({
        type: REMOVE_FOLLOWER_REQUEST,
        data: id,
      });
      setTimeout(() => {
        mutateFollower((prev) => prev.filter((data) => data.id !== id));
      }, 500);
    }
  };
  return (
    <List
      style={{ marginBottom: 20 }}
      grid={{ gutter: 4, xs: 3, md: 4 }}
      size="small"
      header={<div>{header}</div>}
      loadMore={<div style={{ textAlign: 'center', margin: '10px 0' }}><Button onClick={onClickMore} loading={loading}>더 보기</Button></div>}
      bordered
      dataSource={data}
      renderItem={(item) => (
        <List.Item style={{ marginTop: '20px' }}>
          <Card actions={[<StopOutlined key="stop" onClick={onCancel(item.id)} />]}>
            <Card.Meta description={item.nickname} />
          </Card>
        </List.Item>
      )}
    />
  );
};

FollowList.propTypes = {
  header: propTypes.string.isRequired,
  data: propTypes.array.isRequired,
};

export default FollowList;

팔로잉 취소나 팔로워 삭제 시키는 버튼 클릭했을 때 dispatch 실행 이후 DB에서 삭제되기 전에 mutateFollower, mutateFollowing이 실행할 수 있어 해당 팔로잉, 팔로워 사용자 삭제했는데 화면에서 새로고침 전까지 안없어지더라구요 그래서 setTimeout을 사용했는데 다른 방법도 있을까해서 여쭤봅니다! setTimeout으로 사용하기도 하나요?

답변 2

0

재원허님의 프로필 이미지
재원허
질문자

아 죄송해요 그 Profile이랑 FollowList 컴포넌트 코드 수정해서 올렸습니다! mutateFollwer, mutateFollowing useSWR 훅으로부터 받은 뮤테이트 함수입니다!

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

코드 자체는 setTimeout 없어도될것같은데 없을 때 문제가 생긴다면 swr devtools로 확인해야할 것 같습니다.

재원허님의 프로필 이미지
재원허
질문자

swr devtools로 사용해봤습니다! 위 사진처럼 투니 팔로잉 사용자 삭제버튼 클릭했는데 수는 줄어들지만 목록에서느 삭제되지가 않네요ㅠㅠ dispatch가 비동기로 넘어가기 때문에 결과 반환되기 전에 즉, DB에서 팔로워, 팔로잉 사용자 삭제되기 전에 mutateFollowing, mutateFollowers이 실행돼서 DB에서 팔로워, 팔로잉 사용자가 삭제되기 전의 데이터를 받아와버릴 수도 있는 것 아닌가요?ㅠㅠ 그래서 setTimeout으로 500ms 뒤에 동작하도록 설정한거거든요...

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

네 맞습니다. mutate가 디비작업보다 먼저 실행됩니다. 다만 셋타임아웃은 시간을 정확히 보장하지 못하므로 dispatch가 끝난 시점에 해야 합니다.

재원허님의 프로필 이미지
재원허
질문자

그래서 setTimeout 말고 dispatch가 끝나는 시점에 동작할 수 있는 다른 방식 어떤게 있을지 여쭤보고 싶은거였습니다!ㅠㅠ

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

강좌에서 다뤘습니다. unfollowDone 이런 걸 true로 만들고 useEffect에서 true가 되면 호출하는 것을요.

0

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

mutateFollower랑 mutateFollowing이 어디서 난 건가요? 데이터는 또 어디서 오고 어떻게 쓰이고 있나요?