React

 

 

백엔드 노드 서버 구축하기

 

버전이 다르기 때문에 소스가 강좌와 다를 수 있다.

버전

next:  13.0.4

antd:  5.0.1

 

소스 : https://github.dev/braverokmc79/node-bird-sns

 

제로초 소스 : https://github.com/ZeroCho/react-nodebird

 

 

 

 

55. 게시글 불러오기

 

강의 :

https://www.inflearn.com/course/%EB%85%B8%EB%93%9C%EB%B2%84%EB%93%9C-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%A6%AC%EB%89%B4%EC%96%BC/unit/48842?tab=curriculum

 

 

오류 시 주의   try ~ catch 에서  console.log(err); 확인 처리   인트값 확인 , user 정보 값 확인

 

 

백엔드 routes/posts.js

 

비밀번호 제외 user 정보 불러오기

 

const express = require('express');
const { Post, Image, User, Comment } = require('../models');

const router = express.Router();



//GET /posts
router.get('/:limit', async (req, res, next) => {

    try {
        console.log("req.params : ", req.params.limit);
        const posts = await Post.findAll({
            // where: { id: lastId },
            limit: 10,
            order: [
                ['createdAt', 'DESC'],
                [Comment, 'createdAt', 'DESC']
            ],
            offsset: parseInt(req.params.limit),
            //21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1
            include: [{
                model: User,
                attributes: ['id', 'nickname']
            },
            {
                model: Image
            },
            {
                model: Comment,
                include: [{
                    model: User,
                    attributes: ['id', 'nickname']
                }]
            }
            ]
        });

        res.status(200).json(posts);

    } catch (error) {
        console.error("posts error : ", error);
        next(error);
    }

});



module.exports = router;

 

 

 

 

프론트 엔드

saga/post.js

import { all, fork, put, throttle, delay, takeLatest, call } from 'redux-saga/effects';
import axios from 'axios';
import {
    ADD_POST_REQUEST, ADD_POST_SUCCESS, ADD_POST_FAILURE,
    ADD_COMMENT_REQUEST, ADD_COMMENT_SUCCESS, ADD_COMMENT_FAILURE,
    REMOVE_POST_REQUEST, REMOVE_POST_SUCCESS, REMOVE_POST_FAILURE,
    LOAD_POSTS_REQUEST, LOAD_POSTS_SUCCESS, LOAD_POSTS_FAILURE, generateDummyPost
} from '../reducers/post'

import {
    ADD_POST_TO_ME, REMOVE_POST_OF_ME
} from '../reducers/user';




let scrollCount = 0

//무한 스크롤
function loadPostsAPI(data) {
    console.log("DB 데이터 가져오기 :");
    return axios.get(`/posts/${data}`);
}

function* loadPosts(action) {
    try {
        yield delay(1000);
        scrollCount += 10;

        const result = yield call(loadPostsAPI, scrollCount);
        console.log(" 2무한 스크롤  result: ", scrollCount, result.data);
        //        console.log(" 3무한 스크롤  generateDummyPost: ", generateDummyPost(10));


        yield put({
            type: LOAD_POSTS_SUCCESS,
            data: result.data
        });


        // yield put({
        //     type: LOAD_POSTS_SUCCESS,
        //     data: generateDummyPost(10)
        // });

        // if (result !== null) {
        //     yield put({
        //         type: LOAD_POSTS_SUCCESS,
        //         data: result.data
        //     });
        // } else {
        //     yield put({
        //         type: LOAD_POSTS_SUCCESS,
        //         data: generateDummyPost(10)
        //     });
        // }


    } catch (err) {
        console.log(err);
        yield put({
            type: LOAD_POSTS_FAILURE,
            error: err.response.data
        });
    }
}

function* watchLoadPosts() {
    // yield takeLatest(LOAD_POSTS_REQUEST, loadPosts);
    yield throttle(3000, LOAD_POSTS_REQUEST, loadPosts);
}





~

 

 

 

 

 

 

56. 게시글 좋아요

강의 :

https://www.inflearn.com/course/%EB%85%B8%EB%93%9C%EB%B2%84%EB%93%9C-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%A6%AC%EB%89%B4%EC%96%BC/unit/48843?tab=curriculum

 

 

프론트 엔드

 

reducers/post.js

import shortid from 'shortid';
import produce from 'immer';
import { faker } from '@faker-js/faker';

faker.seed(123);

export const initialState = {
    mainPosts: [],
    imagePaths: [],
    hasMorePosts: true,

    unlikePostLoading: false,
    unlikePostDone: false,
    unlikePostError: null,

    likePostLoading: false,
    likePostDone: false,
    likePostError: null,

~




export const LIKE_POST_REQUEST = 'LIKE_POST_REQUEST';
export const LIKE_POST_SUCCESS = 'LIKE_POST_SUCCESS';
export const LIKE_POST_FAILURE = 'LIKE_POST_FAILURE';

export const UNLIKE_POST_REQUEST = 'UNLIKE_POST_REQUEST';
export const UNLIKE_POST_SUCCESS = 'UNLIKE_POST_SUCCESS';
export const UNLIKE_POST_FAILURE = 'UNLIKE_POST_FAILURE';


~






        //LIKE
        case LIKE_POST_REQUEST:
            draft.likePostLoading = true;
            draft.likePostDone = false;
            draft.likePostError = null;
            break;

        case LIKE_POST_SUCCESS: {
            const post = draft.mainPosts.find((v) => v.id === parseInt(action.data.PostId));
            post.Likers.push({ id: action.data.UserId });
            draft.likePostLoading = false;
            draft.likePostDone = true;
            break;
        }

        case LIKE_POST_FAILURE:
            draft.likePostLoading = false;
            draft.likePostError = action.error;
            break;


        //UN LIKE
        case UNLIKE_POST_REQUEST:
            draft.unlikePostLoading = true;
            draft.unlikePostDone = false;
            draft.unlikePostError = null;
            break;

        case UNLIKE_POST_SUCCESS: {
            const post = draft.mainPosts.find((v) => v.id === parseInt(action.data.PostId));
            post.Likers = post.Likers.filter((v) => v.id !== action.data.UserId);
            draft.unlikePostLoading = false;
            draft.unlikePostDone = true;
            break;
        }
        case UNLIKE_POST_FAILURE:
            draft.unlikePostLoading = false;
            draft.unlikePostError = action.error;
            break;




~







 

 

 

saga/post.js

import { all, fork, put, throttle, delay, takeLatest, call } from 'redux-saga/effects';
import axios from 'axios';
import {
    ADD_POST_REQUEST, ADD_POST_SUCCESS, ADD_POST_FAILURE,
    ADD_COMMENT_REQUEST, ADD_COMMENT_SUCCESS, ADD_COMMENT_FAILURE,
    REMOVE_POST_REQUEST, REMOVE_POST_SUCCESS, REMOVE_POST_FAILURE,
    LOAD_POSTS_REQUEST, LOAD_POSTS_SUCCESS, LOAD_POSTS_FAILURE, generateDummyPost,
    LIKE_POST_REQUEST, LIKE_POST_SUCCESS, LIKE_POST_FAILURE,
    UNLIKE_POST_REQUEST, UNLIKE_POST_SUCCESS, UNLIKE_POST_FAILURE,

} from '../reducers/post'

import {
    ADD_POST_TO_ME, REMOVE_POST_OF_ME
} from '../reducers/user';


//Like
function likePostAPI(data) {
    return axios.patch(`/post/${data}/like`);
}

function* likePost(action) {
    try {
        const result = yield call(likePostAPI, action.data);
        yield put({
            type: LIKE_POST_SUCCESS,
            data: result.data
        });
    } catch (err) {
        console.log(err);
        yield put({
            type: LIKE_POST_FAILURE,
            error: err.response.data
        });
    }
}
function* watchLikePost() {
    yield takeLatest(LIKE_POST_REQUEST, likePost);
}


//UN Like
function unlikePostAPI(data) {
    return axios.delete(`/post/${data}/like`);
}
function* unlikePost(action) {
    try {
        const result = yield call(unlikePostAPI, action.data);
        yield put({
            type: UNLIKE_POST_SUCCESS,
            data: result.data
        });
    } catch (err) {
        console.log(err);
        yield put({
            type: UNLIKE_POST_FAILURE,
            error: err.response.data
        });
    }
}
function* watchUnlikePost() {
    yield takeLatest(UNLIKE_POST_REQUEST, unlikePost);
}


~


~
export default function* postSaga() {
    yield all([
        fork(watchAddPost),
        fork(watchAddComment),
        fork(watchRemovePost),
        fork(watchLoadPosts),
        fork(watchLikePost),
        fork(watchUnlikePost)
    ]);
}




 

components/PostCard.js

import React, { useState, useCallback } from 'react';
import { Card, Button, Avatar, Image, Popover, List, Space } from 'antd';
import { Comment } from '@ant-design/compatible';
import { RetweetOutlined, HeartOutlined, MessageOutlined, EllipsisOutlined, HeartTwoTone } from '@ant-design/icons';
import PropTypes from 'prop-types'
import { useSelector, useDispatch } from 'react-redux';
import PostImages from './PostImages';
import CommentForm from './CommentForm';
import { createGlobalStyle } from 'styled-components';
import PostCardContent from './PostCardContent';
import { REMOVE_POST_REQUEST } from '../reducers/post';
import FollowButton from './FollowButton';
import { LIKE_POST_REQUEST, UNLIKE_POST_REQUEST } from '../reducers/post';

const Global = createGlobalStyle`
    .ant-card-actions{
     background: #eeeeee !important;
    }
`;



const PostCard = ({ post }) => {
    const dispatch = useDispatch();
    const { removePostLoading } = useSelector((state) => state.post);
    const [commentFormOpened, setCommentFormOpened] = useState(false);
    const id = useSelector((state) => state.user.me?.id);
    const liked = post.Likers.find((v) => v.id === id);

    const onLike = useCallback(() => {
        dispatch({
            type: LIKE_POST_REQUEST,
            data: post.id
        })
    }, []);


    const onUnlike = useCallback(() => {
        dispatch({
            type: UNLIKE_POST_REQUEST,
            data: post.id
        })
    }, []);

~

 

 

 

 

백엔드

 

시퀄라이즈는 모델을 만들면 다음과 같은 함수를 제공해 준다.

post.addLikers , post.removeLikers 

    Post.associate = (db) => {
        db.Post.belongsTo(db.User); //post.addUser post.getUser , post.setUser
        db.Post.belongsToMany(db.Hashtag, { through: 'PostHashtag' }); //post.addHashtags
        db.Post.hasMany(db.Comment); //post.addComments, post.getComments
        db.Post.hasMany(db.Image); //post.addImages . post.getImages
        db.Post.belongsToMany(db.User, { through: 'Like', as: 'Likers' }); //post.addLikers , post.removeLikers
        db.Post.belongsTo(db.Post, { as: "Retweet" }); //post.addReteet
    }

 

댓글 등록 및 불러올시에  좋아요 누른 사람  컬럼값 가져오기  Likers 

    }, {
                model: User, //좋아요 누른 사람       
                as: 'Likers',
                attributes: ['id']
            }
            ]

 

routes/post.js

const express = require('express');
const { Post, User, Image, Comment } = require('../models');
const { isLoggedIn, isNotLoggedIn } = require('./middlewares');
const router = express.Router();


//** passport 특성상 로그인 하면, 라우터 접근시 항상 deserializeUser 실행해서 req.user 를 만든다.  req.user.id로 접근해서 정보를 가져올 수 있다.
//POST  /post
router.post('/', isLoggedIn, async (req, res, next) => {
    try {
        const post = await Post.create({
            content: req.body.content,
            UserId: req.user.id
        });

        const fullPost = await Post.findOne({
            where: { id: post.id },
            include: [{
                model: Image,
            },
            {
                model: Comment,
                include: [{
                    model: User, //댓글 작성자
                    attributes: ['id', 'nickname']
                }]
            }, {
                model: User,  //게시글 작성자
                attributes: ['id', 'nickname']

            }, {
                model: User, //좋아요 누른 사람       
                as: 'Likers',
                attributes: ['id']
            }
            ]
        })

        res.status(201).json(fullPost);
    } catch (error) {
        console.error(" Post 에러 :  ", error);
        next(error);
    }
});



//POST 댓글  /post
router.post('/:postId/comment', isLoggedIn, async (req, res, next) => {     //POST /post/1/comment
    try {

        const post = await Post.findOne({
            where: { id: req.params.postId }
        });

        if (!post) {
            return res.status(403).send("존재하지 않는 게시글입니다.");
        }

        const comment = await Comment.create({
            content: req.body.content,
            PostId: req.params.postId,
            UserId: req.user.id
        });

        const fullComment = await Comment.findOne({
            where: { id: comment.id },
            include: [{
                model: User,
                attributes: ['id', 'nickname']
            }]
        })

        //console.log(" 댓글 작성 완료 : ", comment);

        res.status(201).json(fullComment);
    } catch (error) {
        console.error(" comment 에러 :  ", error);
        next(error);
    }
});



router.patch('/:postId/like', async (req, res, next) => {  //PATcH  /post/1/like
    try {
        const post = await Post.findOne({
            where: { id: req.params.postId }
        });

        if (!post) {
            return res.status(403).send("게시글이 존재하지 않습니다.");
        }

        await post.addLikers(req.user.id);
        res.json({ PostId: post.id, UserId: req.user.id })
    } catch (error) {
        console.error(error);
        next(error);
    }

});

router.delete('/:postId/like', async (req, res, next) => {  //DELETE  /post/1/like
    try {
        const post = await Post.findOne({
            where: { id: req.params.postId }
        });

        if (!post) {
            return res.status(403).send("게시글이 존재하지 않습니다.");
        }

        await post.removeLikers(req.user.id);
        res.json({ PostId: post.id, UserId: req.user.id })
    } catch (error) {
        console.error(error);
        next(error);
    }

})



//DELETE  /post
router.delete('/', (req, res) => {
    res.json({ id: 1 })
});



module.exports = router;

 

 

 

 

 

 

 

57. 게시글 제거 / 닉네임 변경

강의 :

https://www.inflearn.com/course/%EB%85%B8%EB%93%9C%EB%B2%84%EB%93%9C-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%A6%AC%EB%89%B4%EC%96%BC/unit/48844?tab=curriculum

 

백엔드

 routes/post.js

~

//게시글 삭제 DELETE  /post/10
router.delete('/:postId', isLoggedIn, async (req, res, next) => {
    console.log(" 게시글 삭제 : ", req.params.postId);
    console.log(" 패스포트 정보: ", req.user.id);
    try {
        await Post.destroy({
            where: {
                id: req.params.postId,
                UserId: req.user.id
            },
        });
        res.status(200).json({ PostId: parseInt(req.params.postId, 10) })
    } catch (error) {
        console.error(error);
        next(error);
    }
});


~

 

routes/user.js

 

~


router.patch('/nickname', isLoggedIn, async (req, res, next) => {
    try {
        await User.update({
            nickname: req.body.nickname,
        }, {
            where: {
                id: req.user.id
            }
        });
        res.status(200).json({ nickname: req.body.nickname })
    } catch (error) {
        console.error(error);
        next(error);
    }
})

module.exports = router;

 

 

 

 

프론트엔드

components/NicknameEditForm.js

import React, { useMemo, useCallback } from 'react';
import { Form, Input } from 'antd';
import { useDispatch, useSelector } from 'react-redux';
import { CHANGE_NICKNAME_REQUEST } from './../reducers/user';
import useInput from '../hooks/useInput';

const NicknameEditForm = () => {
    const { me } = useSelector((state) => state.user);
    const [nickname, onChangeNickname] = useInput(me?.nickname || '');
    const dispatch = useDispatch();

    const onSubmit = useCallback(() => {
        dispatch({
            type: CHANGE_NICKNAME_REQUEST,
            data: nickname
        })

    }, [nickname]);

    const style = useMemo(() => ({
        marginBottom: '20px',
        border: "1px solid #d9d9d9",
        padding: "20px"
    }, []));


    return (
        <Form style={style} >
            <Input.Search
                value={nickname}
                onChange={onChangeNickname}
                addonBefore="닉네임"
                enterButton="수정"
                onSearch={onSubmit}
            />
        </Form>
    );
};

export default NicknameEditForm;

 

 

reducer/user.js

~

        //닉네임변경
        case CHANGE_NICKNAME_REQUEST:
            draft.changeNicknameLoading = true;
            draft.changeNicknameDone = false;
            draft.changeNicknameError = null;
            break;

        case CHANGE_NICKNAME_SUCCESS:
            draft.me.nickname = action.data.nickname;
            draft.changeNicknameLoading = false;
            draft.changeNicknameDone = true;
            break;

        case CHANGE_NICKNAME_FAILURE:
            draft.changeNicknameLoading = false;
            draft.changeNicknameError = action.error;
            break;


~

 

saga/user.js

import { all, fork, put, takeLatest, delay, call, throttle } from 'redux-saga/effects';
import axios from 'axios';
import {
    LOG_IN_REQUEST, LOG_IN_SUCCESS, LOG_IN_FAILURE,
    LOG_OUT_REQUEST, LOG_OUT_SUCCESS, LOG_OUT_FAILURE,
    SIGN_UP_REQUEST, SIGN_UP_SUCCESS, SIGN_UP_FAILURE,
    FOLLOW_REQUEST, FOLLOW_SUCCESS, FOLLOW_FAILURE,
    UNFOLLOW_REQUEST, UNFOLLOW_SUCCESS, UNFOLLOW_FAILURE,
    LOAD_MY_INFO_REQUEST, LOAD_MY_INFO_SUCCESS, LOAD_MY_INFO_FAILURE,
    CHANGE_NICKNAME_REQUEST, CHANGE_NICKNAME_SUCCESS, CHANGE_NICKNAME_FAILURE
} from '../reducers/user';


//닉네임 변경
function changeNicknameAPI(data) {
    return axios.patch('/user/nickname', {nickname:data});
}
function* changeNickname(action) {
    try {
        const result = yield call(changeNicknameAPI, action.data)

        yield put({
            type: CHANGE_NICKNAME_SUCCESS,
            data: result.data
        });

    } catch (err) {
        yield put({
            type: CHANGE_NICKNAME_FAILURE,
            error: err.response.data
        });
    }
}
function* watchChangeNickname() {
    yield takeLatest(CHANGE_NICKNAME_REQUEST, changeNickname);
}



~

 

saga/post.js

~

//게시글 삭제
function removePostAPI(data) {
    return axios.delete(`/post/${data}`, data);
}

function* removePost(action) {
    try {

        console.log("게시글 삭제 :", action.data);
        const result = yield call(removePostAPI, action.data);

        yield put({
            type: REMOVE_POST_SUCCESS,
            data: result.data
        });

        yield put({
            type: REMOVE_POST_OF_ME,
            data: action.data
        })

    } catch (err) {
        console.log(err);
        yield put({
            type: REMOVE_POST_FAILURE,
            error: err.response.data
        });
    }
}

function* watchRemovePost() {
    yield takeLatest(REMOVE_POST_REQUEST, removePost);
}



~

 

 

 

 

 

 

 

 

58. 팔로우/언팔로우

강의 :

https://www.inflearn.com/course/%EB%85%B8%EB%93%9C%EB%B2%84%EB%93%9C-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%A6%AC%EB%89%B4%EC%96%BC/unit/48845?tab=curriculum

 

 

 

백엔드 :

routes/users.js

~


//팔로우 추가
router.patch('/:userId/follow', isLoggedIn, async (req, res, next) => { //PATCH  /user/1/follow
    try {
        const user = await User.findOne({ where: { id: req.params.userId } });
        if (!user) {
            res.status(403).send('없은 사람을 팔로우하려고 하시네요?');
        }

        await user.addFollowers(req.user.id)
        res.status(200).json({ UserId: parseInt(req.params.userId, 10) })
    } catch (error) {
        console.error(error);
        next(error);
    }

});


//팔로우 취소 ==> 팔로잉 제거
router.delete('/:userId/follow', isLoggedIn, async (req, res, next) => { //DELETE  /user/1/follow
    try {
        const user = await User.findOne({ where: { id: req.params.userId } });
        console.log(" 팔로잉 제거  : ", req.params.userId);

        if (!user) {
            res.status(403).send('없은 사람을 제거하려고  하시네요?');
        }
        //const me = await User.findOne({ where: { id: req.user.id } });
        //제거 하려는 아이디 req.params.userId  이다.
        //현재 접속 한 나의 아이디는  패스포트로 req.user.id 이다.
        //따라서, and  조건으로 findOne me 가져오고 으로 제거하려는 req.params.userId  넣으면 된다.
        //DELETE FROM `Follow` WHERE `FollowerId` = 2 AND `FollowingId` IN (1)
        //await me.removeFollowings(req.params.userId);

        //=>  반대로 팔로워 제거로 해도 된다.
        await user.removeFollowers(req.user.id);

        res.status(200).json({ UserId: parseInt(req.params.userId, 10) })
    } catch (error) {
        console.error(error);
        next(error);
    }
});


//팔로우 차단 => 팔로워 제거
router.delete('/follower/:userId', isLoggedIn, async (req, res, next) => { //DELETE  /user/1/follow
    try {
        console.log(" 팔로워 제거  : ", req.params.userId);
        const user = await User.findOne({ where: { id: req.params.userId } });
        if (!user) {
            res.status(403).send('없은 사람을 팔로우차단 하려고 하시네요?');
        }
        // const me = await User.findOne({ where: { id: req.user.id } });
        // await me.removeFollowers(req.params.userId);

        //=>  반대로 팔로잉 제거로 한번만 실행 처리
        await user.removeFollowings(req.user.id);

        res.status(200).json({ UserId: parseInt(req.params.userId, 10) })
    } catch (error) {
        console.error(error);
        next(error);
    }
});






//팔로워 불러워기
router.get('/followers', isLoggedIn, async (req, res, next) => { //get  /user/followers
    try {
        //패스포트에 로그인한 user id  값 :  req.user.id
        const user = await User.findOne({ where: { id: req.user.id } });
        const followers = await user.getFollowers();
        res.status(200).json(followers)
    } catch (error) {
        console.error(error);
        next(error);
    }
});



// 팔로잉 불러워기 시퀄라이즈에서 다음과 같이 Followings  처리를 해서  getFollowings 적용 됨
router.get('/followings', isLoggedIn, async (req, res, next) => { //get  /user/followings
    try {
        const user = await User.findOne({ where: { id: req.user.id } });
        const followings = await user.getFollowings();
        res.status(200).json(followings);
    } catch (error) {
        console.error(error);
        next(error);
    }
});




module.exports = router;

 

 

 

프론트 엔드

components/FollowButton.js

~

    if (post.User.id === me.id) {
        return null;
    }



~

 

 

 

components/FollowList.js

 

import React from 'react';
import PropTypes from 'prop-types'
import { Button, Card, List } from 'antd';
import { StopOutlined } from '@ant-design/icons'
import { useDispatch } from 'react-redux';
import { REMOVE_FOLLOW_REQUEST, UNFOLLOW_REQUEST } from '../reducers/user';


const FollowList = ({ header, data }) => {
    const dispatch = useDispatch();

    //반목문에서 고차함수 사용으로  파라미터 값을 전달시킬 수 있다 (고차 함수(Higher order function)는 함수를 인자로 전달받거나 함수를 결과로 반환하는 함수)
    const onCancle = (id) => () => {
        if (header === '팔로잉') {
            dispatch({
                type: UNFOLLOW_REQUEST,
                data: id
            })
        } else if (header === '팔로워') {
            dispatch({
                type: REMOVE_FOLLOW_REQUEST,
                data: id
            })
        }
    };

    return (
        <List
            style={{ marginBottom: 20 }}
            grid={{ gutter: 4, xs: 2, md: 3 }}
            size="small"
            header={<div>{header}</div>}
            loadMore={<div style={{ textAlign: 'center', margin: '10px 0' }}><Button>더 보기</Button></div>}
            bordered
            dataSource={data}
            renderItem={(item) => (
                <List.Item style={{ marginTop: 20 }}>
                    <Card actions={[<StopOutlined key="stop" onClick={onCancle(item.id)} />]}  >
                        <Card.Meta description={item.nickname} />
                    </Card>
                </List.Item >
            )
            }
        >
        </List >
    );
};

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


export default FollowList;

 

 

 

pages/profile.js

import React, { useEffect, useCallback } from 'react';
import AppLayout from './../components/AppLayout';
import Head from 'next/head';
import { useDispatch, useSelector } from 'react-redux';
import Router from 'next/router';
import FollowList from './../components/FollowList';
import NicknameEditForm from './../components/NicknameEditForm';
import { LOAD_FOLLOWERS_REQUEST, LOAD_FOLLOWINGS_REQUEST } from '../reducers/user';


const Profile = () => {
    const { me } = useSelector((state) => state.user);
    const dispatch = useDispatch();

    useEffect(() => {
        dispatch({
            type: LOAD_FOLLOWERS_REQUEST
        });

        dispatch({
            type: LOAD_FOLLOWINGS_REQUEST
        });

    }, [LOAD_FOLLOWERS_REQUEST, LOAD_FOLLOWINGS_REQUEST])


~

 

 

 

reducers/users.js

import produce from "immer";

export const initialState = {
    unfollowLoading: false,//언팔로우 시도중
    unfollowDone: false,
    unfollowError: null,

    removeFollowLoading: false,//팔로워 차단 시도중
    removeFollowDone: false,
    removeFollowError: null,



    followLoading: false,//팔로우 시도중
    followDone: false,
    followError: null,

    logInLoading: false,//로그인 시도중
    logInDone: false,
    logInError: null,

    logOutLoading: false, //로그아웃 시도중
    logOutDone: false,
    logOutError: null,

    signUpLoading: false, //회원가입 시도중
    signUpDone: false,
    signUpError: null,

    changeNicknameLoading: false, //닉네임 변경
    changeNicknameDone: false,
    changeNicknameError: null,

    loadMyInfoLoading: false, //브라우저 새로고침시  유저정보 가져오기
    loadMyInfoDone: false,
    loadMyInfoError: null,

    loadFollowersLoading: false, //팔로워 목록 가져오기
    loadFollowersDone: false,
    loadFollowersError: null,

    loadFollowingsLoading: false, //팔로잉 목록 가져오기
    loadFollowingsDone: false,
    loadFollowingsError: null,


    me: null,
    signUpdata: {},
    loginData: {}
}


export const LOG_IN_REQUEST = "LOG_IN_REQUEST";
export const LOG_IN_SUCCESS = "LOG_IN_SUCCESS";
export const LOG_IN_FAILURE = "LOG_IN_FAILURE";

export const LOG_OUT_REQUEST = "LOG_OUT_REQUEST";
export const LOG_OUT_SUCCESS = "LOG_OUT_SUCCESS";
export const LOG_OUT_FAILURE = "LOG_OUT_FAILURE";

export const SIGN_UP_REQUEST = "SIGN_UP_REQUEST";
export const SIGN_UP_SUCCESS = "SIGN_UP_SUCCESS";
export const SIGN_UP_FAILURE = "SIGN_UP_FAILURE";

export const CHANGE_NICKNAME_REQUEST = "CHANGE_NICKNAME_REQUEST";
export const CHANGE_NICKNAME_SUCCESS = "CHANGE_NICKNAME_SUCCESS";
export const CHANGE_NICKNAME_FAILURE = "CHANGE_NICKNAME_FAILURE";

export const FOLLOW_REQUEST = "FOLLOW_REQUEST";
export const FOLLOW_SUCCESS = "FOLLOW_SUCCESS";
export const FOLLOW_FAILURE = "FOLLOW_FAILURE";

//팔로잉 제거
export const UNFOLLOW_REQUEST = "UNFOLLOW_REQUEST";
export const UNFOLLOW_SUCCESS = "UNFOLLOW_SUCCESS";
export const UNFOLLOW_FAILURE = "UNFOLLOW_FAILURE";

//팔로워 차단  = > 팔로워 제거
export const REMOVE_FOLLOW_REQUEST = "REMOVE_FOLLOW_REQUEST";
export const REMOVE_FOLLOW_SUCCESS = "REMOVE_FOLLOW_SUCCESS";
export const REMOVE_FOLLOW_FAILURE = "REMOVE_FOLLOW_FAILURE";



export const ADD_POST_TO_ME = 'ADD_POST_TO_ME';
export const REMOVE_POST_OF_ME = 'REMOVE_POST_OF_ME';

export const LOAD_MY_INFO_REQUEST = "LOAD_MY_INFO_REQUEST";
export const LOAD_MY_INFO_SUCCESS = "LOAD_MY_INFO_SUCCESS";
export const LOAD_MY_INFO_FAILURE = "LOAD_MY_INFO_FAILURE";


export const LOAD_FOLLOWERS_REQUEST = "LOAD_FOLLOWERS_REQUEST";
export const LOAD_FOLLOWERS_SUCCESS = "LOAD_FOLLOWERS_SUCCESS";
export const LOAD_FOLLOWERS_FAILURE = "LOAD_FOLLOWERS_FAILURE";

export const LOAD_FOLLOWINGS_REQUEST = "LOAD_FOLLOWINGS_REQUEST";
export const LOAD_FOLLOWINGS_SUCCESS = "LOAD_FOLLOWINGS_SUCCESS";
export const LOAD_FOLLOWINGS_FAILURE = "LOAD_FOLLOWINGS_FAILURE";







export const loginRequestAction = (data) => {
    return {
        type: LOG_IN_REQUEST,
        data
    }
}

export const logoutRequestAction = () => {
    return {
        type: LOG_OUT_REQUEST
    }
}




const reducer = (state = initialState, action) => produce(state, (draft) => {

    switch (action.type) {

        //팔로워 목록 가져오기
        case LOAD_FOLLOWERS_REQUEST:
            draft.loadFollowersLoading = true;
            draft.loadFollowersDone = false;
            draft.loadFollowersError = null;
            break;

        case LOAD_FOLLOWERS_SUCCESS:
            draft.loadFollowersLoading = false;
            draft.loadFollowersDone = true;
            draft.me.Followers = action.data;
            break;

        case LOAD_FOLLOWERS_FAILURE:
            draft.loadFollowersLoading = false;
            draft.loadFollowersError = action.error;
            break;


        //팔로잉 목록 가져오기
        case LOAD_FOLLOWINGS_REQUEST:
            draft.loadFollowingsLoading = true;
            draft.loadFollowingsDone = false;
            draft.loadFollowingsError = null;
            break;

        case LOAD_FOLLOWINGS_SUCCESS:
            draft.loadFollowingsLoading = false;
            draft.loadFollowingsDone = true;
            draft.me.Followings = action.data
            break;

        case LOAD_FOLLOWINGS_FAILURE:
            draft.loadFollowingsLoading = false;
            draft.loadFollowingsError = action.error;
            break;



        //브라우저 새로고침시  유저정보 가져오기
        case LOAD_MY_INFO_REQUEST:
            draft.loadMyInfoLoading = true;
            draft.loadMyInfoDone = false;
            draft.loadMyInfoError = null;
            break;

        case LOAD_MY_INFO_SUCCESS:
            draft.loadMyInfoLoading = false;
            draft.loadMyInfoDone = true;
            draft.me = action.data
            break;

        case LOAD_MY_INFO_FAILURE:
            draft.loadMyInfoLoading = false;
            draft.loadMyInfoError = action.error;
            break;


        //팔로우
        case FOLLOW_REQUEST:
            draft.followLoading = true;
            draft.followDone = false;
            draft.followError = null;
            break;

        case FOLLOW_SUCCESS:
            draft.followLoading = false;
            draft.followDone = true;
            draft.me.Followings.push({ id: action.data.UserId });
            break;

        case FOLLOW_FAILURE:
            draft.followLoading = false;
            draft.followError = action.error;
            break;

        //언팔로우 => 팔로잉 제거
        case UNFOLLOW_REQUEST:
            draft.unfollowLoading = true;
            draft.unfollowDone = false;
            draft.unfollowError = null;
            break;

        case UNFOLLOW_SUCCESS:
            draft.unfollowLoading = false;
            draft.unfollowDone = true;
            draft.me.Followings = draft.me.Followings.filter((v) => v.id !== action.data.UserId);
            break;

        case UNFOLLOW_FAILURE:
            draft.unfollowLoading = false;
            draft.unfollowError = action.error;
            break;


        //팔로워 차단 => 팔로워 제거
        case REMOVE_FOLLOW_REQUEST:
            draft.removeFollowLoading = true;
            draft.removeFollowDone = false;
            draft.removeFollowError = null;
            break;

        case REMOVE_FOLLOW_SUCCESS:
            draft.removeFollowLoading = false;
            draft.removeFollowDone = true;
            draft.me.Followers = draft.me.Followers.filter((v) => v.id !== action.data.UserId);
            break;

        case REMOVE_FOLLOW_FAILURE:
            draft.removeFollowLoading = false;
            draft.removeFollowError = action.error;
            break;




        //로그인
        case LOG_IN_REQUEST:
            draft.logInLoading = true;
            draft.logInDone = false;
            draft.logInError = null;
            break;

        case LOG_IN_SUCCESS:
            draft.logInLoading = false;
            draft.logInDone = true;
            draft.me = action.data;
            break;

        case LOG_IN_FAILURE:
            draft.logInLoading = false;
            draft.logInError = action.error;
            break;



        //로그 아웃
        case LOG_OUT_REQUEST:
            draft.logOutLoading = true;
            draft.logOutDone = false;
            draft.logOutError = null;
            break;

        case LOG_OUT_SUCCESS:
            draft.logOutLoading = false;
            draft.logOutDone = true;
            draft.me = null;
            break;

        case LOG_OUT_FAILURE:
            draft.logOutLoading = false;
            draft.logOutError = action.error;
            draft;



        //회원가입
        case SIGN_UP_REQUEST:
            console.log(" 회원 가입 리듀서 ");
            draft.signUpLoading = true;
            draft.signUpDone = false;
            draft.signUpError = null;
            break;

        case SIGN_UP_SUCCESS:
            draft.signUpLoading = false;
            draft.signUpDone = true;
            break;

        case SIGN_UP_FAILURE:
            draft.signUpLoading = false;
            draft.signUpError = action.error;
            break;


        //닉네임변경
        case CHANGE_NICKNAME_REQUEST:
            draft.changeNicknameLoading = true;
            draft.changeNicknameDone = false;
            draft.changeNicknameError = null;
            break;

        case CHANGE_NICKNAME_SUCCESS:
            draft.me.nickname = action.data.nickname;
            draft.changeNicknameLoading = false;
            draft.changeNicknameDone = true;
            break;

        case CHANGE_NICKNAME_FAILURE:
            draft.changeNicknameLoading = false;
            draft.changeNicknameError = action.error;
            break;



        //게시글 수 숫자 증가
        case ADD_POST_TO_ME:
            draft.me.Posts.unshift({ id: action.data });
            break;

        // return {
        //     ...state,
        //     me: {
        //         ...state.me,
        //         Posts: [{ id: action.data }, ...state.me.Posts]
        //     }
        // };


        //게시글 수 숫자 감소
        case REMOVE_POST_OF_ME:
            draft.me.Posts = draft.me.Posts.filter((v) => v.id !== action.data);
            break;

        // return {
        //     ...state,
        //     me: {
        //         ...state.me,
        //         Posts: state.me.Posts.filter((v) => v.id !== action.data),
        //     }
        // }

        default:
            break;
    }



});


export default reducer;


 

saga/user.js

import { all, fork, put, takeLatest, delay, call, throttle } from 'redux-saga/effects';
import axios from 'axios';
import {
    LOG_IN_REQUEST, LOG_IN_SUCCESS, LOG_IN_FAILURE,
    LOG_OUT_REQUEST, LOG_OUT_SUCCESS, LOG_OUT_FAILURE,
    SIGN_UP_REQUEST, SIGN_UP_SUCCESS, SIGN_UP_FAILURE,
    FOLLOW_REQUEST, FOLLOW_SUCCESS, FOLLOW_FAILURE,
    UNFOLLOW_REQUEST, UNFOLLOW_SUCCESS, UNFOLLOW_FAILURE,
    LOAD_MY_INFO_REQUEST, LOAD_MY_INFO_SUCCESS, LOAD_MY_INFO_FAILURE,
    CHANGE_NICKNAME_REQUEST, CHANGE_NICKNAME_SUCCESS, CHANGE_NICKNAME_FAILURE,
    LOAD_FOLLOWERS_SUCCESS, LOAD_FOLLOWERS_FAILURE, LOAD_FOLLOWERS_REQUEST,
    LOAD_FOLLOWINGS_REQUEST, LOAD_FOLLOWINGS_SUCCESS, LOAD_FOLLOWINGS_FAILURE,
    REMOVE_FOLLOW_REQUEST, REMOVE_FOLLOW_SUCCESS, REMOVE_FOLLOW_FAILURE

} from '../reducers/user';


//팔로워 목록 가져오기
function loadFollowersAPI(data) {
    return axios.get('/user/followers', data);
}
function* loadFollowers(action) {
    try {
        const result = yield call(loadFollowersAPI, action.data)

        yield put({
            type: LOAD_FOLLOWERS_SUCCESS,
            data: result.data
        });

    } catch (err) {
        yield put({
            type: LOAD_FOLLOWERS_FAILURE,
            error: err.response.data
        });
    }
}
function* watchLoadFollowers() {
    yield takeLatest(LOAD_FOLLOWERS_REQUEST, loadFollowers);
}


//팔로잉 목록 가져오기
function loadFollowingsAPI(data) {
    return axios.get('/user/followings', data);
}
function* loadFollowings(action) {
    try {
        const result = yield call(loadFollowingsAPI, action.data)

        yield put({
            type: LOAD_FOLLOWINGS_SUCCESS,
            data: result.data
        });

    } catch (err) {
        yield put({
            type: LOAD_FOLLOWINGS_FAILURE,
            error: err.response.data
        });
    }
}
function* watchLoadFollowings() {
    yield takeLatest(LOAD_FOLLOWINGS_REQUEST, loadFollowings);
}




//닉네임 변경
function changeNicknameAPI(data) {
    return axios.patch('/user/nickname', { nickname: data });
}
function* changeNickname(action) {
    try {
        const result = yield call(changeNicknameAPI, action.data)

        yield put({
            type: CHANGE_NICKNAME_SUCCESS,
            data: result.data
        });

    } catch (err) {
        yield put({
            type: CHANGE_NICKNAME_FAILURE,
            error: err.response.data
        });
    }
}
function* watchChangeNickname() {
    yield takeLatest(CHANGE_NICKNAME_REQUEST, changeNickname);
}




//브라우저 새로고침시  유저정보 가져오기
function loadMyInfoAPI() {
    return axios.get('/user');
}
function* loadMyInfo(action) {
    try {
        const result = yield call(loadMyInfoAPI)

        yield put({
            type: LOAD_MY_INFO_SUCCESS,
            data: result.data
        });

    } catch (err) {
        yield put({
            type: LOAD_MY_INFO_FAILURE,
            error: err.response.data
        });
    }
}
function* watchLoadMyInfo() {
    yield takeLatest(LOAD_MY_INFO_REQUEST, loadMyInfo);
}





//팔로우 
function followAPI(data) {
    return axios.patch(`/user/${data}/follow`);
}
function* follow(action) {
    try {
        const result = yield call(followAPI, action.data);

        yield put({
            type: FOLLOW_SUCCESS,
            data: result.data
        });

    } catch (err) {
        yield put({
            type: FOLLOW_FAILURE,
            error: err.response.data
        });
    }
}
function* watchFollow() {
    yield takeLatest(FOLLOW_REQUEST, follow);
}



//언팔로우 => 팔로잉 제거
function unfollowAPI(data) {
    return axios.delete(`/user/${data}/follow`);
}

function* unfollow(action) {
    try {
        const result = yield call(unfollowAPI, action.data);

        yield put({
            type: UNFOLLOW_SUCCESS,
            data: result.data
        });

    } catch (err) {
        yield put({
            type: UNFOLLOW_FAILURE,
            error: err.response.data
        });
    }
}
function* watchUnFollow() {
    yield takeLatest(UNFOLLOW_REQUEST, unfollow);
}


//팔로워 차단 => 팔로워 제거
function removeFollowAPI(data) {
    return axios.delete(`/user/follower/${data}`);
}

function* removeFollow(action) {
    try {
        const result = yield call(removeFollowAPI, action.data);

        yield put({
            type: REMOVE_FOLLOW_SUCCESS,
            data: result.data
        });

    } catch (err) {
        yield put({
            type: REMOVE_FOLLOW_FAILURE,
            error: err.response.data
        });
    }
}
function* watchRemoveFollow() {
    yield takeLatest(REMOVE_FOLLOW_REQUEST, removeFollow);
}






//1-1.로그인 처리
function logInAPI(data) {
    return axios.post('/user/login', data);
}


// const l = login({ type: "LOG_IN_REQUEST", data: { id: 'test@gmail.com' } });
// l.next();
// l.next();

//1-2.로그인 처리
function* login(action) {
    //put 을 dispatch
    //call 은 동기 함수 호출
    //fork 는 비동기 함수 호출
    try {

        const result = yield call(logInAPI, action.data);
        //yield delay(1000);

        yield put({
            type: LOG_IN_SUCCESS,
            data: result.data
        });

        console.log("5. 미들웨어로 사가 로그인 호출 result.data  : ", result.data);

    } catch (err) {
        yield put({
            type: LOG_IN_FAILURE,
            error: err.response.data
        });
    }
}

//1-3 로그인 처리
function* watchLogIn() {
    //LOG_IN 실행 될때 까지 기다리겠다.
    console.log("2. watchLogIn ");
    yield takeLatest(LOG_IN_REQUEST, login);
}







//2-1.로그아웃 처리
function logOutAPI() {
    console.log(" 로그 아웃 !");
    return axios.post('/user/logout');
}



//2-2.로그아웃 처리
function* logOut(action) {
    //put 을 dispatch
    //call 은 동기 함수 호출
    //fork 는 비동기 함수 호출
    try {

        yield call(logOutAPI);

        yield put({
            type: LOG_OUT_SUCCESS
        });
    } catch (err) {
        yield put({
            type: LOG_OUT_FAILURE,
            error: err.response.data
        });
    }
}

//2-3 로그아웃 처리
function* watchLogOut() {
    yield takeLatest(LOG_OUT_REQUEST, logOut);
}









//3. 회원가입
function signUpAPI(data) {
    return axios.post('/user', data);
}

function* signUp(action) {
    try {
        const result = yield call(signUpAPI, action.data);
        //yield delay(1000);
        console.log(" 회원 가입  : ", result);

        yield put({
            type: SIGN_UP_SUCCESS,
        });

    } catch (err) {
        console.log(" 사가 에러 : ", err.response.data);
        yield put({
            type: SIGN_UP_FAILURE,
            error: err.response.data
        });
    }
}


function* watchSignUp() {
    yield takeLatest(SIGN_UP_REQUEST, signUp);
}





//all 하면 한방에 배열로 적은 함수들이 실행처리 된다.
//fork , call 로 실행한다. all 은 fork 나 call 을 동시에 실행시키도록 한다.
//call 은 동기 함수 호출
//fork 는 비동기 함수 호출
export default function* userSaga() {
    yield all([
        fork(watchLoadFollowers),
        fork(watchLoadFollowings),
        fork(watchLogIn),
        fork(watchLogOut),
        fork(watchFollow),
        fork(watchUnFollow),
        fork(watchSignUp),
        fork(watchLoadMyInfo),
        fork(watchChangeNickname),
        fork(watchRemoveFollow),
    ])
}

 

 

 

 

 

react

 

about author

PHRASE

Level 60  라이트

잉크로 쓴 거짓이 피로 쓴 진실을 덮을 수 없다. -노신

댓글 ( 4)

댓글 남기기

작성