본문으로 바로가기

TIL 2021-12-21 Redux ToolKit과 createAsyncThunk, Figma 아이콘 제작

category TIL 2021. 12. 22. 00:12

 

6N23RP Redux로 변환

React Context로 되어있던 프로젝트를 Redux로 변환하였다.

 

가장 큰 이유는 SSR을 지원한다는 점이다.

 

현재 Firebase에서 Authentication을 하면 0.5초정도 번쩍이는, flash하는 현상이 있다.

매 페이지마다 firebase 서버가 유저를 verify를 하기 떄문에 발생하는 일이다.

이미 Next.js로 넘어온 상태기 때문에 SSR 상태에서 verify를 하면 flash가 일어나지 않을 것이라고 생각했고 Redux로 넘어가기로 하였다.

 

Recoil등의 라이브러리도 수동으로 조작하면 SSR을 지원하는 것 같긴 하나, Next.js 환경에서는 어떨지 모르고,

무엇보다 너무 레퍼런스가 될만한 자료가 없다.

 

이러한 판단하에 Redux로 migrate하게 되었다.

 

현재 firebase는 클라이언트 측에서의 작동만 고려하고 있어서 SSR 상태에서는 token verify하려면 firestore-admin을 추가로 설치해주어야했다.

 

firebase를 쓰다보면 참 답답한 구석이 많은데 사용량 적으면 무료니까 다 참을수 있다..

 

 

https://taegon.kim/archives/10263

 

 

Recoil 레시피: 서버 사이드 렌더링

이제 Recoil.js 사용의 마지막 단원, 서버 사이드 렌더링(Server-side rendering)까지 왔다. 사실 서버 사이드 렌더링은 React 애플리케이션에 있어 필수 기능은 아니다. 검색 엔진 최적화(SEO)나 초기 렌더

taegon.kim

 

https://stackoverflow.com/questions/60156164/how-to-stop-firebase-re-auth-on-every-page-reload-in-a-react-app

 

How to stop firebase re-auth() on every page reload in a react app?

So i have this great react app using firebase auth and firestore. Everything working fine except Whenever i reload the page while a user is already logged in... navbar links change for a second. Lo...

stackoverflow.com

 

 

 

Redux ToolKit, extraReducer

RTK의 createSlice를 사용하면서 extraReducer라는 항목이 존재했다. 이는 자신의 slice가 아닌 외부의 action에 대해서 처리를 할 수 있게 해주는 reducer다.

 

const {actions,reducer} = createSlice({
  name:'users',
  reducers:{
    ......
  },
  initialState:.... ,
  extraReducers:(builder) => {
    builder.addCase(authActions.logout, state => {
      state.isLoggedIn = false;
    });
  }
});

위와 같이 authActions은 다른 slice의 action이다. 이처럼 다른 action에 대해서 처리를 할 수 있다.

 

const fetchUserById = createAsyncThunk(
  'users/fetchByIdStatus',
  async (userId, thunkAPI) => {
    const response = await userAPI.fetchById(userId)
    return response.data
  }
)

const {actions,reducer} = createSlice({
  name:'users',
  reducers:{
    ......
  },
  initialState:.... ,
  extraReducers: (builder) => {
    ......
    ......

    builder.addCase(fetchUserById.pending, (state, action) => {
      state.loading=true;
      state.whoseDataIsLoading = action.payload;
    })
  }
});

위와 같이 createAsyncThunk 같이 새롭게 만든 thunk 함수에 대해서도 처리를 할 수 있다.

 

 

export const subjectSlice = createSlice({
    name: 'subject',

    initialState: {} as any,

    reducers: {
        setEnt(state, action) {
            return action.payload;
        },
    },

    extraReducers: {
        [HYDRATE]: (state, action) => {
            console.log('HYDRATE', state, action.payload);
            return {
                ...state,
                ...action.payload.subject,
            };
        },
    }

next-redux wrapper에서도 RTK에서 Hydrate을 다룰때 extraReducer를 사용한다.

 

여담이지만 createSlice 너무 편하다!

 

 

createAsyncThunk를 통한 Modal 구현

 

Redux로 바꾼 김에 React Context를 통해 구현한 Modal을 createAsyncThunk를 통해 바꾸기로 하였다.

 

const fetchUserById = createAsyncThunk(
  'users/fetchByIdStatus',
  async (userId, thunkAPI) => {
    const response = await userAPI.fetchById(userId)
    return response.data
  }
)

// Then, handle actions in your reducers:
const usersSlice = createSlice({
  name: 'users',
  initialState: { entities: [], loading: 'idle' },
  reducers: {
    // standard reducer logic, with auto-generated action types per reducer
  },
  extraReducers: (builder) => {
    // Add reducers for additional action types here, and handle loading state as needed
    builder.addCase(fetchUserById.fulfilled, (state, action) => {
      // Add user to the state array
      state.entities.push(action.payload)
    })
  },
})

위와 같이 비동기 Thunk 함수를 만들어주는 함수로 pending, reject, fullfilled 상태를 extraReducer에서 다뤄줄수도 있다.

 

서버 통신에 사용하기도 하지만, Modal이 떴을때 수락/ 거절을 누를때 Promise를 resolve하는 식으로 thunk를 사용할 수도 있다.

 

모달이 뜨면 pending, 수락을 누르면 fullfilled, 거절하면 reject

 

export const confirmationAsyncThunk = {
    open: createAsyncThunk<
        boolean,
        DialogContentProp,
        { extra: ThunkExtraArguments }
    >('dialogStatus', async (payload, { extra: { store }, dispatch }) => {
        dispatch(openDialog(payload));
        return new Promise<boolean>((resolve) => {
            const unsubscribe = store.subscribe(() => {
                const { dialogReducer } = store.getState() as RootState;
                if (dialogReducer.isConfirmed) {
                    unsubscribe();
                    resolve(true);
                }
                if (dialogReducer.isDeclined) {
                    unsubscribe();
                    resolve(false);
                }
            });
        });
    }),
};
  const hasConfirm = await openDialog({
                content: '추가하시겠습니까',
            });
            if (hasConfirm) {
                addQuery.mutate();
            }

위와 같은 식으로 구현하였다.

 

React Context에서 구현했던 방식은 추가, 삭제, 수정 등의 action을 행하면 Modal 컴포넌트에서 이에 따라 행동을 취하는 방식이었다.

const ModalAction = async () => {
        if (user === null) {
            signInWithGoogle();
            CloseModal();
            return;
        }
        CloseModal();
        switch (modalAction) {
            case 'add':
                addQuery.mutate();
                break;
            case 'delete':
                deleteQuery.mutate();
                break;
            case 'edit':
                editQuery.mutate();
                break;
            default:
                alert('알수 없는 행동입니다!');
                break;
        }
    const modalText: { [key: string]: { description: string } } = {
        add: {
            description: '추가하시겠습니까?',
        },
        delete: {
            description: '삭제하시겠습니까?',
        },
        edit: {
            description: '수정하시겠습니까?',
        },
        notuser: {
            description: '정보를 수정하기 위해서는 로그인해야합니다',
        },

이 방식은 확정성 면에서 좋지 않았고, 또 Modal 컴포넌트에서 수락 이후의 행동을 정했기에 직관적이지 못했다.

 

반면에 createAsyncThunk를 사용한 현재 방식은 Modal을 열고 연 이후의 행동들을 동일한 파일에서 작성할 수 있으므로 훨씬 이 Modal에서의 행동을 직관적으로 볼 수 있다.

 

별도의 Hook으로 나누어 관리하기도 편해진다.

이번에 리팩토링하면서 느끼는 것인데 해당 컴포넌트의 state와 관련된 함수를 제외한 다른 함수들은 로직을 별도의 Hook으로 분리하는 것이 훨씬 깔끔하다는 생각이 든다.

 

 

https://stackoverflow.com/questions/67213381/generic-modals-with-redux-and-thunk

 

Generic modals with Redux and Thunk

I've been looking into creating generic modals with React, Redux, and Thunk. Ideally, my state would look like the following: export interface ConfirmModalState { isOpened: boolean; onConfirm: ...

stackoverflow.com

 

FirebaseApp name [DEFAULT] already exists!

대충 이런 문제가 발생했다.

 

if (firebase.apps.length === 0) {
    firebase.initializeApp({});
}

기존 firebase 이전에서는 이것을 통해 firebase가 이중으로 초기화하지 않도록 해주면 되었는데,

함수형으로 바뀐 9 와서는 어떻게 해야할지를 몰라 애를 먹었다.

 

 

import { initializeApp, getApps, getApp } from "firebase/app";
getApps().length === 0 ? initializeApp(firebaseConfig) : getApp();

결론적으로는 이런 식으로 getApps() 함수를 통해서 길이를 확인해주면 된다.

 

 

 

 

SEO 최적화, Figma 아이콘 만들기

 

 

6N23RP 사이트에 들어갈 아이콘 및 사진들을 제작하는 중이다.

 

오늘 배운 것은 Text를 벡터화할 수 있다는 것인데, CTRL + E를 통해서 벡터화가 가능하다

 

https://www.figma.com/file/ESpmua683XMr0iBDjZpDpr/Neon-effect-(Community)?node-id=490%3A1 

 

Figma

Created with Figma

www.figma.com

위의 예시를 통해 알게 되었다.