기존에는 모달을 useState로 객체를 생성해서 조작하는 방식을 사용했었는데 문제가 발생했다.
원래 Detail.jsx 컴포넌트에서만 사용하던 것을 Submit.jsx 컴포넌트에서도 똑같이 사용하려니 이미 Detail에서 선언해둔 state를 Submit에서 또 그대로 다시 선언해야하는 상황이 발생한것. 게다가 모달로 전달해서 사용하는 onClick에 사용되는 함수들조차 똑같이 재선언 해야하는 상황에 직면했다.
props로 state나 함수를 전달하여 사용하는 방법도 물론 있지만 그렇게 하면 props-drilling을 피할 수 없는 상황이기에 useState사용을 포기하고 redux로 관리하고자 한다.
const initialState = null;
//action types
export const RESET = "modal/RESET";
export const ACTIVATE = "modal/ACTIVATE";
//action creator
export const activateModal = (configObj) => ({
type: ACTIVATE,
payload: configObj,
});
export const resetModal = () => ({
type: RESET,
});
//리듀서
const modalControl = (state = initialState, action) => {
switch (action.type) {
case ACTIVATE:
return { ...action.payload };
case RESET:
return null;
default:
return state;
}
};
export default modalControl;
기존에 사용했던 코드를 최소한으로 수정하면서 최대한 재사용 하기 위해 위와 같이 리듀서를 만들었다.
initialState를 null 로 설정한것은 initialState의 falsy/truthy 값을 토대로 모달을 조건부 렌더링 하기 때문이다.
initialState가 null 일때 모달창이 사라지므로 type RESET일때 null을 return 하게 되어있고, type ACTIVATE일때는 payload로 전달받은 객체를 그대로 리듀서에서 리턴하고 이 값을 읽어들여서 모달창의 세부사항이 정해지도록 설정했다.
//root reducer 설정
import { combineReducers } from "redux";
import fanLetter from "redux/modules/fanletter";
import chosenMember from "redux/modules/chosen-member";
import modalControl from "redux/modules/modal-control";
import { persistReducer } from "redux-persist";
import storage from "redux-persist/lib/storage";
const persistConfig = {
key: "root",
storage,
whitelist: ["fanLetter"],
};
const rootReducer = combineReducers({
fanLetter,
chosenMember,
modalControl,
});
export default persistReducer(persistConfig, rootReducer);
// store 설정
import { createStore } from "redux";
import { composeWithDevTools } from "redux-devtools-extension";
import { persistStore } from "redux-persist";
import rootReducer from "./root-reducer";
export const store = createStore(rootReducer, composeWithDevTools());
export const persistor = persistStore(store);
import { useDispatch, useSelector } from "react-redux";
import { activateModal } from "redux/modules/modal-control";
//필요한 것들을 먼저 import 해주고
//컴포넌트 함수 내에서
const modalControl = useSelector((state) => state.modalControl);
const dispatch = useDispatch();
//필요한 상수 선언을 해준 이후
const submitHandler = (e) => {
e.preventDefault();
dispatch(
activateModal({
title: "메시지 등록",
message: "메시지가 등록되었습니다! 감사합니다 ❤️",
})
);
//모달창이 열려야 하는 부분에서 dispatch로 모달 설정용 객체를 리듀서로 전달
//이후 JSX 리턴문에서
{modalControl && (
<ReusableModal
title={modalControl.title}
message={modalControl.message}
btnMsg={modalControl.btnMsg}
btnFn={modalControl.btnFn}
/>
)}
//이런식으로 modalControl 객체에 설정되어 있는 값을 모달컴포넌트에서 전달받아 사용한다
사실상 기존에 state와 setState를 사용했던 것과 동일한 로직이다.
바뀐 점은 setState 대신 dispatch와 action creator 함수를 사용한다는 점과
state 대신 useSelector로 지정한 변수를 사용한다는 점 뿐이다.
동일한 useState를 여러 컴포넌트에서 반복적으로 사용하는 것을 방지할 수 있다. redux에서 전역 state로 관리하기 때문.
ReusableModal 컴포넌트에서 사용하는 함수중 일부를 ReusableModal내에 선언할 수 있다. 기존에는 ReusableModal이 사용되는 모든 컴포넌트에 각각 따로 선언했어야 했다.
const closeModal = () => {
dispatch(resetModal());
};
위 코드가 예시인데, 모달창을 닫기 위한 코드를 모달창을 사용하는 모든 컴포넌트에 사용했었다. 해당 컴포넌트 내에서 선언된 state를 조작해야 했기 때문이다.
하지만 redux를 도입하면서 다른 컴포넌트에서도 얼마든지 디스패치로 state를 제어할 수 있기 때문에 더 논리적으로 납득이 가는 장소에서 간단하게 관리가 가능해졌다.