Skip to content

Instantly share code, notes, and snippets.

@velopert
Created July 14, 2019 01:56
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save velopert/d418517581e580c5191f385cf8f2f3b0 to your computer and use it in GitHub Desktop.
Save velopert/d418517581e580c5191f385cf8f2f3b0 to your computer and use it in GitHub Desktop.
React Context + useReducer + async actions example
// 이 함수는 파라미터로 액션의 타입 (예: GET_USER) 과 Promise 를 만들어주는 함수를 받아옵니다.
export function createAsyncDispatcher(type, promiseFn) {
// 성공, 실패에 대한 액션 타입 문자열을 준비합니다.
const SUCCESS = `${type}_SUCCESS`;
const ERROR = `${type}_ERROR`;
// 새로운 함수를 만듭니다.
// ...rest 를 사용하여 나머지 파라미터를 rest 배열에 담습니다.
async function actionHandler(dispatch, ...rest) {
dispatch({ type }); // 요청 시작됨
try {
const data = await promiseFn(...rest); // rest 배열을 spread 로 넣어줍니다.
dispatch({
type: SUCCESS,
data
}); // 성공함
} catch (e) {
dispatch({
type: ERROR,
error: e
}); // 실패함
}
}
return actionHandler; // 만든 함수를 반환합니다.
}
export const initialAsyncState = {
loading: false,
data: null,
error: null
};
// 로딩중일 때 바뀔 상태 객체
const loadingState = {
loading: true,
data: null,
error: null
};
// 성공했을 때의 상태 만들어주는 함수
const success = data => ({
loading: false,
data,
error: null
});
// 실패했을 때의 상태 만들어주는 함수
const error = error => ({
loading: false,
data: null,
error: error
});
// 세가지 액션을 처리하는 리듀서를 만들어줍니다
// type 은 액션 타입, key 는 리듀서서 사용할 필드 이름입니다 (예: user, users)
export function createAsyncHandler(type, key) {
// 성공, 실패에 대한 액션 타입 문자열을 준비합니다.
const SUCCESS = `${type}_SUCCESS`;
const ERROR = `${type}_ERROR`;
// 함수를 새로 만들어서
function handler(state, action) {
switch (action.type) {
case type:
return {
...state,
[key]: loadingState
};
case SUCCESS:
return {
...state,
[key]: success(action.data)
};
case ERROR:
return {
...state,
[key]: error(action.error)
};
default:
return state;
}
}
// 반환합니다
return handler;
}
import React, { createContext, useReducer, useContext } from 'react';
import {
createAsyncDispatcher,
createAsyncHandler,
initialAsyncState
} from './asyncActionUtils';
import * as api from './api'; // api 파일에서 내보낸 모든 함수들을 불러옴
// UsersContext 에서 사용 할 기본 상태
const initialState = {
users: initialAsyncState,
user: initialAsyncState
};
const usersHandler = createAsyncHandler('GET_USERS', 'users');
const userHandler = createAsyncHandler('GET_USER', 'user');
// 위에서 만든 객체 / 유틸 함수들을 사용하여 리듀서 작성
function usersReducer(state, action) {
switch (action.type) {
case 'GET_USERS':
case 'GET_USERS_SUCCESS':
case 'GET_USERS_ERROR':
return usersHandler(state, action);
case 'GET_USER':
case 'GET_USER_SUCCESS':
case 'GET_USER_ERROR':
return userHandler(state, action);
default:
throw new Error(`Unhanded action type: ${action.type}`);
}
}
// State 용 Context 와 Dispatch 용 Context 따로 만들어주기
const UsersStateContext = createContext(null);
const UsersDispatchContext = createContext(null);
// 위에서 선언한 두가지 Context 들의 Provider 로 감싸주는 컴포넌트
export function UsersProvider({ children }) {
const [state, dispatch] = useReducer(usersReducer, initialState);
return (
<UsersStateContext.Provider value={state}>
<UsersDispatchContext.Provider value={dispatch}>
{children}
</UsersDispatchContext.Provider>
</UsersStateContext.Provider>
);
}
// State 를 쉽게 조회 할 수 있게 해주는 커스텀 Hook
export function useUsersState() {
const state = useContext(UsersStateContext);
if (!state) {
throw new Error('Cannot find UsersProvider');
}
return state;
}
// Dispatch 를 쉽게 사용 할 수 있게 해주는 커스텀 Hook
export function useUsersDispatch() {
const dispatch = useContext(UsersDispatchContext);
if (!dispatch) {
throw new Error('Cannot find UsersProvider');
}
return dispatch;
}
export const getUsers = createAsyncDispatcher('GET_USERS', api.getUsers);
export const getUser = createAsyncDispatcher('GET_USER', api.getUser);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment