리액트 - 사용자 경험개선 - 포스트 데이터상태 구조 변경

정영찬·2022년 4월 25일
0

리액트

목록 보기
68/79
post-custom-banner

이제 재로딩 현상이라던가. 이전에 선택한 데이터의 모습이 지워지지않고 남아있는 현상도 해결했지만. 사용자가 전에 선택해서 열람한 데이터를 다시 클릭할 경우 다시 로딩을 해야하는데 이 로딩현상을 제거하고자 한다.

post 의 상태관리는 아래와 같은 구조로 되어있다.

post: {
 	data,
    loading: false,
    error: null,
}

post는 특정 id에 따라 data 값이 달라지는데 이 상태구조로 관리하게 되면 data가 덮어씌워주게 되어서 data의 재사용이 어렵다. 따라서 상태 구조를 변경하려고 한다. 최종적으로는 각각의 id에 존재하는 data,loading,error의 상태를 가지고 있게끔 만들고 싶다.

먼저 posts.js 모듈에서 thunk 생성함수인 getPost를 수정한다. 여기서는 createPromiseThunk 를 사용하지 않고 작성하며 meta 값으로 id를 추가한다.

export const getPost = id => async dispatch => {
  dispatch({ type: GET_POST, meta: id});
  try {
    const payload = await postsAPI.getPostById(id);
    dispatch({type : GET_POST_SUCCESS, payload, meta: id})
  } catch(e) {
    dispatch({
      type: GET_POST_ERROR,
      payload: e,
      error: true,
      meta: id
    });
  }
}

initialSTate 에서 post의 경우는 비어있는 객체로 지정한다.

const initialState = {
posts: reducerUtils.initial(),
post: {}
};

그리고 getPostReducer의 내용을 수정한다. GET_POST의 경우는 state.post[id]가 있다면 state.post[id].data를 가져오고 그렇지 않으면 undefined가 되는 식으로 수정했다.

const getPostReducer = (state, action) => {
 const id = action.meta;
 switch(action.type) {
   case GET_POST:
     return {
       ...state,
       post: {
         ...state.post,
         [id]: reducerUtils.loading(state.post[id] && state.post[id].data)        
       }
     };
   case GET_POST_SUCCESS:
     return {
       ...state,
       post: {
         ...state.post,
         [id]: reducerUtils.success(action.payload)
       }
     }
   case GET_POST_ERROR:
     return{
       ...state,
       post: {
         ...state.post,
         [id]:reducerUtils.error(action.payload)
       }
     }
   
   default:
     return state;
   }    
 }

이에 맞춰서 postContainer도 수정이 필요하다.
data, loading, error 를 비구조화 할당을 할때 state.posts.post[postId]에서 진행을 하는데, 맨 처음에는 이값이 undefined이기 때문에 오류가 발생할 수 있다. 따라서 이전의 데이터가 아직 들어있지 않으면 오른쪽의 reducerUtils.initial에서 비구조화 할당을 진행하게 된다.

const {data, loading, error } = useSelector(state => state.posts.post[postId] || reducerUtils.initial())

useEffect에서 이전의 데이터 모습을 보여주지 않기위해 사용했던 clearPost를 제거한다. 왜냐하면 post의 데이터를 해당 id마다 기억해야하는데 clearPost로 인해 전부 제거되어버리면 다시 로딩해야하기 때문이다.
loading 이 true 이거나 data가 false일 때만 로딩중이라는 문구가 나타나게 하면 수정은 완료된다.

const PostContainer = ({postId}) => {
    const {data, loading, error } = useSelector(state => state.posts.post[postId] || reducerUtils.initial())
    const dispatch = useDispatch();

    useEffect(()=> {
        dispatch(getPost(postId))
        
    },[postId, dispatch]);

    if (loading && !data) return <div>로딩중...</div>
    if (error) return <div>에러 발생!</div>
    if (!data) return null;
  return (
    <Post post={data}/>
  )
}

결과

이제 한번 클릭한 항목도 다시 로딩되지 않고 바로 데이터를 보여주게 된다. data의 값을 덮어씌우지 않고, 선택한 항목의 id마다 서로 다른 data를 기억하고 있기 때문이다.


redux devtool로 확인해보면 알수있듯이 만약 사용자가 첫번째 항목만 클릭해놓으면 post내부에는 첫번째 항목의 data를 기억해놓게 된다.
그리고. 다른 항목들마저 클릭하면 해당 id의 data도 저장되는 모습을 알수 있다.

전부 클릭한 경우 3개의 항목이 추가된 모습을 알수 있다.

콘솔창을 자세히 보면, 데이터가 이미 저장되어있는데도 dispatch요청이 진행되는데, 이를 없애고 싶으면? data가 있을때 그냥 return 시켜버리면 된다! 저번에 했던것과 비슷하다.

 useEffect(()=> {
        if(data) return;
        dispatch(getPost(postId))
        
    },[postId, dispatch,data]);

요렇게!

profile
개발자 꿈나무
post-custom-banner

0개의 댓글