React - Github-finder (2)

김정욱·2020년 11월 2일
0

React

목록 보기
14/22
post-thumbnail
post-custom-banner

# Input Reducer 생성 #

1) /modules/input.js 작성
   : input 리듀서를 만들고 그 안에 username이라는 state 생성

import { createAction, handleActions } from 'redux-actions';

const CHANGE_INPUT = 'input/CHANGE_INPUT';

export const changeInput = createAction(CHANGE_INPUT, (username)=>({
    username,
}));

const initState = {
    username: "",
};

const input = handleActions(
    {
        [CHANGE_INPUT]: (state, { payload: {username}}) => ({
            ...state,
            username,
        }),
    },
    initState
)

export default input;

2) rootReducer에 추가

import { combineReducers } from "redux";
import { all } from "redux-saga/effects";
import input from './input';

const rootReducer = combineReducers({
  input,
});

export default rootReducer;

3) InputContainer.js 에서 사용
   : input 리듀서에 포함된 username이라는 state를 사용하기 위해
     useDispatch()와 useSelector()를 사용

import React, {useRef} from 'react';
import InputComponent from '../components/InputComponent';
import {useDispatch, useSelector} from "react-redux";
import {changeInput} from '../modules/input';

function InputContainer() {

    const dispatch = useDispatch();

    /* 입력받는 값을 실시간으로 username에 업데이트 */
    const onHandleInputChange = (e)=>{
        const value = e.target.value;
        dispatch(changeInput(value));
    };

    return (
        <>
            <InputComponent
            onHandleInputChange={onHandleInputChange}>
            </InputComponent>
        </>
    )
}

export default InputContainer

# user Reducer 생성 & 사용 #


1) /modules/user.js 작성

import { createAction, handleActions } from 'redux-actions';
import createRequestSaga from "../lib/createRequestSaga";
import { createRequestActionTypes } from "../lib/createRequestSaga";
import * as userAPI from "../lib/api/user";
import { takeLatest } from "redux-saga/effects";

/* GET_USER에 대한 성공,실패 액션을 생성 */
const [GET_USER, GET_USER_SUCCESS, GET_USER_FAILURE] = createRequestActionTypes(
    "user/GET_USER"
);
const [GET_REPOS, GET_REPOS_SUCCESS, GET_REPOS_FAILURE] = createRequestActionTypes(
    "user/GET_REPOS"
);

/* 액션 호출 함수 생성 */
export const getUser = createAction(GET_USER, (username) => username);
export const getRepo = createAction(GET_REPOS, (username) => username);

/* 해당하는 액션 호출시 Saga실행 */
const getUserSaga = createRequestSaga(GET_USER, userAPI.userInfo);
const getRepoSaga = createRequestSaga(GET_REPOS, userAPI.userRepo);

/* 요청된 것들 중 가장 마지막 요청만 처리 (여러번 클릭시 모두 처리되면 매우 비효율적!) */
export function* userSaga(){
    yield takeLatest(GET_USER, getUserSaga);
    yield takeLatest(GET_REPOS, getRepoSaga);
}

/* State 초기값 */
const initState = {
    userInfo: null,
    error: null,
    reposInfo: null,
}

/* 액션을 store에 저장하는 리듀서를 handleActions로 쉽게 처리! */
const user = handleActions(
    {
        [GET_USER_SUCCESS]: (state, { payload: userInfo }) => ({
            ...state,
            userInfo,
          }),
          [GET_USER_FAILURE]: (state, { payload: error }) => ({
            ...state,
            error,
          }),
          [GET_REPOS_SUCCESS]: (state, { payload: reposInfo }) => ({
            ...state,
            reposInfo,
          }),
          [GET_REPOS_FAILURE]: (state, { payload: error }) => ({
            ...state,
            error,
          }),
    },
    initState
);

export default user;

2) rootReducer에 추가

import { combineReducers } from "redux";
import { all } from "redux-saga/effects";
import input from './input';
import user, {userSaga} from './user';

const rootReducer = combineReducers({
  input,
  user
});

/* Saga사용을 위해 생성한 userSaga()를 추가! */
export function* rootSaga() {
  yield all([userSaga()]);
}

export default rootReducer;

3) InputContainer.js 수정
   : Form 작성 후 Submit 될때 getUser() / getRepo() 추가

function InputContainer() {
    const dispatch = useDispatch();
    //const nameInput = useRef();

    /* dispatch로 값 넘기기 위해 useSelector로 값 가져옴 */
    const {username} = useSelector(({input})=>({
        username: input.username,
    }));

    const onHandleInputChange = (e)=>{
        const value = e.target.value;
        dispatch(changeInput(value));
    };
     /* 추가된 부분의 핵심 */
    const onHandleFormSubmit = (e) => {
        e.preventDefault();
        dispatch(getUser(username));
        dispatch(getRepo(username));
    }

    return (
        <>
            <InputComponent
            onHandleInputChange={onHandleInputChange}
            onHandleFormSubmit={onHandleFormSubmit}
            username={username}

            </InputComponent>
        </>
    )
}

4) ResultContainer.js수정
   : 저장된 userInfo / reposInfo state를 Component에게 전달

import React from 'react'
import ResultComponent from '../components/ResultComponent';
import { useSelector } from "react-redux";

function ResultContainer() {

     /* 필요한 state들을 useSelector로 store에서 가져온다 */
    const {userInfo, reposInfo, loading} = useSelector(({user, loading})=>({
        userInfo: user.userInfo,
        reposInfo: user.reposInfo,
        loading: loading["user/GET_REPOS"],
    }));

    return (
        <>
            <ResultComponent loading={loading} userInfo={userInfo} reposInfo={reposInfo}>
            </ResultComponent>  
        </>
    )
}

export default ResultContainer

5) ResultComponent.js 수정
   : Container에서 보낸 userInfo와 reposInfo를 적절하게 출력 (조건부 렌더링)

function SearchResult({userInfo, reposInfo, loading}) {
    return (
        <>
            {
                userInfo && 
                    <CardTemplate>
                        <Avatar src={userInfo.avatar_url}/>
                        <UserInfo>
                            <h2>{userInfo.name}</h2>
                            <p>{userInfo.bio}</p>
                            <FollowTemplate>
                                <Follow><strong>Followers</strong> {userInfo.followers}</Follow>
                                <Follow><strong>Following</strong> {userInfo.following}</Follow>
                                <Follow><strong>Repos</strong> {userInfo.public_repos}</Follow>
                            </FollowTemplate>
                            <div>
                            {
                                reposInfo ? 
                                       reposInfo.slice(0,10).map((repo, idx)=>(
                                            <Repo key={idx} href={repo.html_url} target="_blank">{repo.name}</Repo>
                                        ))
                                        : (loading == true? <Loading>LOADING</Loading>:null)
                                   
                            }
                            </div>
                        </UserInfo>
                    </CardTemplate>
            }
        </>
    );
}
export default SearchResult

# loading Reducer #

  • 원리 : loading 리듀서를 만든 뒤 Saga 사용에 삽입
  • startLoading() / finishLoading()본 요청 전/후에 수행하여 state관리!

1) /modules/loading.js
   : startLoading() / finishLoading()적절한 리듀서 함수 제작

import { createAction, handleActions } from "redux-actions";

const START_LOADING = "loading/START_LOADING";
const FINISH_LOADING = "loading/FINISH_LOADING";

export const startLoading = createAction(
  START_LOADING,
  (requestType) => requestType
);

export const finishLoading = createAction(
  FINISH_LOADING,
  (requestType) => requestType
);

const initState = {};

const loading = handleActions(
  {
    [START_LOADING]: (state, action) => ({
      ...state,
     [action.payload]: true,
    }),
    [FINISH_LOADING]: (state, action) => ({
      ...state,
     [action.payload]: false,
    }),
  },
  initState
);

export default loading;

2) /lib/createRequestSaga.js 작성
   : 앞서 getUserSaga / getRepoSaga를 만들 때 처럼 어떤 요청을 할 때에도
     createRequestSaga를 사용하게 되기 때문에, 요청의 앞/뒤로
     startLoading() / finishLoading() 수행!

import { put, call } from "redux-saga/effects";
import { startLoading, finishLoading } from "../modules/loading";

export const createRequestActionTypes = (type) => {
  const SUCCESS = `${type}_SUCCESS`;
  const FAILURE = `${type}_FAILURE`;
  return [type, SUCCESS, FAILURE];
};

export default function createRequestSage(type, request) {
  const SUCCESS = `${type}_SUCCESS`;
  const FAILURE = `${type}_FAILURE`;
  return function* (action) {

    /* 요청 전에 값을 true로 변환 */
    yield put(startLoading(type));

    try {
      const response = yield call(request, action.payload);
      yield put({
        type: SUCCESS,
        payload: response.data,
        meta: response,
      });
    } catch (error) {
      yield put({
        type: FAILURE,
        payload: error,
        error: true,
      });
    }

      /* 요청이 끝나고 값을 false로 변환 */
    yield put(finishLoading(type));
  };
}

3) 컴포넌트에서 loading 값 가져와서 사용
  : store에서 값을 가져올 때 특정 액션에 대한 loading을 가져오며 값은 true 또는 false


    : loading state에 따라서 조건부 렌더링을 실시

profile
Developer & PhotoGrapher
post-custom-banner

0개의 댓글