From fd67fa30be65a23f67c8082bb925cfede80b01aa Mon Sep 17 00:00:00 2001 From: JoStar33 Date: Tue, 20 Aug 2024 18:46:14 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20zustand=EB=A1=9C=20=EB=A7=88=EC=9D=B4?= =?UTF-8?q?=EA=B7=B8=EB=A0=88=EC=9D=B4=EC=85=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 63 +++++++++++++++----------- package.json | 4 +- src/App.tsx | 15 +++--- src/components/common/LabelWrapper.tsx | 1 + src/components/common/Modal.tsx | 27 ++++++----- src/containers/signUp/index.tsx | 6 +-- src/hooks/useErrorHandler.ts | 8 ++-- src/hooks/useLoading.ts | 16 +------ src/stores/loading.ts | 16 +++---- src/stores/modal.ts | 50 +++++++++----------- 10 files changed, 97 insertions(+), 109 deletions(-) diff --git a/package-lock.json b/package-lock.json index eaa86e6..e64c2cb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,12 +24,12 @@ "react-hook-form": "^7.51.2", "react-icons": "^5.0.1", "react-router-dom": "^6.22.3", - "recoil": "^0.7.7", "styled-components": "^6.1.8", "styled-reset": "^4.5.2", "use-mask-input": "^3.3.7", "yet-another-react-lightbox": "^3.21.4", - "yup": "^1.4.0" + "yup": "^1.4.0", + "zustand": "^4.5.5" }, "devDependencies": { "@types/crypto-js": "^4.2.2", @@ -3377,11 +3377,6 @@ "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } }, - "node_modules/hamt_plus": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/hamt_plus/-/hamt_plus-1.0.2.tgz", - "integrity": "sha512-t2JXKaehnMb9paaYA7J0BX8QQAY8lwfQ9Gjf4pg/mk4krt+cmwmU652HOoWonf+7+EQV97ARPMhhVgU1ra2GhA==" - }, "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -4267,25 +4262,6 @@ "react-dom": ">=16.8" } }, - "node_modules/recoil": { - "version": "0.7.7", - "resolved": "https://registry.npmjs.org/recoil/-/recoil-0.7.7.tgz", - "integrity": "sha512-8Og5KPQW9LwC577Vc7Ug2P0vQshkv1y3zG3tSSkWMqkWSwHmE+by06L8JtnGocjW6gcCvfwB3YtrJG6/tWivNQ==", - "dependencies": { - "hamt_plus": "1.0.2" - }, - "peerDependencies": { - "react": ">=16.13.1" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - }, - "react-native": { - "optional": true - } - } - }, "node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", @@ -4813,6 +4789,14 @@ "react-dom": ">=16.4 || 17 || 18 || 20" } }, + "node_modules/use-sync-external-store": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz", + "integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/vite": { "version": "5.2.6", "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.6.tgz", @@ -5037,6 +5021,33 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zustand": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.5.tgz", + "integrity": "sha512-+0PALYNJNgK6hldkgDq2vLrw5f6g/jCInz52n9RTpropGgeAf/ioFUCdtsjCqu4gNhW9D01rUQBROoRjdzyn2Q==", + "dependencies": { + "use-sync-external-store": "1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } } } } diff --git a/package.json b/package.json index f5c4176..f1fd0c2 100644 --- a/package.json +++ b/package.json @@ -26,12 +26,12 @@ "react-hook-form": "^7.51.2", "react-icons": "^5.0.1", "react-router-dom": "^6.22.3", - "recoil": "^0.7.7", "styled-components": "^6.1.8", "styled-reset": "^4.5.2", "use-mask-input": "^3.3.7", "yet-another-react-lightbox": "^3.21.4", - "yup": "^1.4.0" + "yup": "^1.4.0", + "zustand": "^4.5.5" }, "devDependencies": { "@types/crypto-js": "^4.2.2", diff --git a/src/App.tsx b/src/App.tsx index eb5d7b0..9206f9d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,13 +1,12 @@ import GlobalStyle from '@/styles/GlobalStyles'; import Theme from '@/styles/Theme'; import Layout from '@/components/layouts'; -import { useRecoilValue } from 'recoil'; -import { modalWithText } from '@/stores/modal'; +import { useModalStore } from '@/stores/modal'; import Portal from '@/components/common/Portal'; import Modal from '@/components/common/Modal'; import { AnimatePresence } from 'framer-motion'; import Router from '@/Router'; -import { loadingState } from './stores/loading'; +import { useLoadingStore } from './stores/loading'; import Loading from './components/common/Loading'; import DarkBackground from './components/common/DarkBackground'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; @@ -16,9 +15,9 @@ import databaseInitializer from './mocks/fakeDatabase'; import React from 'react'; export default function App() { + const { isLoading } = useLoadingStore(); + const { modalState } = useModalStore(); const queryClient = new QueryClient(queryClientDefaultOptions); - const modalWithTextValue = useRecoilValue(modalWithText); - const loadingValue = useRecoilValue(loadingState); React.useEffect(() => { databaseInitializer(); @@ -33,9 +32,9 @@ export default function App() { - {modalWithTextValue.type === 'ALERT' && } - {modalWithTextValue.type === 'CONFIRM' && } - {loadingValue.isLoading && ( + {modalState.type === 'ALERT' && } + {modalState.type === 'CONFIRM' && } + {isLoading && ( diff --git a/src/components/common/LabelWrapper.tsx b/src/components/common/LabelWrapper.tsx index 13bdd0c..3263179 100644 --- a/src/components/common/LabelWrapper.tsx +++ b/src/components/common/LabelWrapper.tsx @@ -19,6 +19,7 @@ export default function LabelWrapper({ label, labelStyle, wrapperStyle, isHorizo flexDirection: 'column', gap: '10px', }; + return (

diff --git a/src/components/common/Modal.tsx b/src/components/common/Modal.tsx index fbaed0a..9879433 100644 --- a/src/components/common/Modal.tsx +++ b/src/components/common/Modal.tsx @@ -1,10 +1,9 @@ import styled from 'styled-components'; import { motion } from 'framer-motion'; -import { useRecoilValue } from 'recoil'; import Portal from '@/components/common/Portal'; import DarkBackground from '@/components/common/DarkBackground'; import { textEllipsis } from '@/styles/Common'; -import { modalWithText } from '@/stores/modal'; +import { useModalStore } from '@/stores/modal'; import { IoMdInformationCircleOutline } from 'react-icons/io'; const modalVariants = { @@ -27,8 +26,8 @@ const modalVariants = { }; function Alert() { - const modalValue = useRecoilValue(modalWithText); - const computedDescTextArr = modalValue.descText ? modalValue.descText.split('\n') : ''; + const { modalState } = useModalStore(); + const computedDescTextArr = modalState.descText ? modalState.descText.split('\n') : ''; return ( @@ -38,13 +37,13 @@ function Alert() { -

{modalValue.titleText}

+

{modalState.titleText}

{computedDescTextArr ? computedDescTextArr.map((desc) =>

{desc}

) : computedDescTextArr}
- @@ -53,8 +52,8 @@ function Alert() { } function Confirm() { - const modalValue = useRecoilValue(modalWithText); - const computedDescTextArr = modalValue.descText ? modalValue.descText.split('\n') : ''; + const { modalState } = useModalStore(); + const computedDescTextArr = modalState.descText ? modalState.descText.split('\n') : ''; return ( @@ -64,17 +63,17 @@ function Confirm() { -

{modalValue.titleText}

+

{modalState.titleText}

{computedDescTextArr ? computedDescTextArr?.map((desc) =>

{desc}

) : computedDescTextArr}
- -
diff --git a/src/containers/signUp/index.tsx b/src/containers/signUp/index.tsx index 7c2e005..ba4fbcb 100644 --- a/src/containers/signUp/index.tsx +++ b/src/containers/signUp/index.tsx @@ -3,20 +3,18 @@ import SignUp from '@/components/signUp'; import routerPath from '@/constants/routerPath'; import { useErrorHandler } from '@/hooks/useErrorHandler'; import useLoading from '@/hooks/useLoading'; -import { modalState } from '@/stores/modal'; +import { useModalStore } from '@/stores/modal'; import { ISignUpForm } from '@/types/auth'; import { schema } from '@/utils/validate/schema'; import { yupResolver } from '@hookform/resolvers/yup'; import { FormProvider, SubmitHandler, useForm } from 'react-hook-form'; import { useNavigate } from 'react-router-dom'; -import { useResetRecoilState, useSetRecoilState } from 'recoil'; import styled from 'styled-components'; export default function SignUpContainer() { const { isLoading, setIsLoading } = useLoading(); const { handleError } = useErrorHandler(); - const setModalState = useSetRecoilState(modalState); - const resetModalState = useResetRecoilState(modalState); + const { resetModalState, setModalState } = useModalStore(); const navigate = useNavigate(); const methods = useForm({ defaultValues: { diff --git a/src/hooks/useErrorHandler.ts b/src/hooks/useErrorHandler.ts index 224958e..4fa1449 100644 --- a/src/hooks/useErrorHandler.ts +++ b/src/hooks/useErrorHandler.ts @@ -1,11 +1,10 @@ -import { modalState } from '@/stores/modal'; +import { useModalStore } from '@/stores/modal'; import { DefaultResponse } from '@/types'; import axios, { AxiosError } from 'axios'; -import { useResetRecoilState, useSetRecoilState } from 'recoil'; export function useErrorHandler() { - const setModalState = useSetRecoilState(modalState); - const resetModalState = useResetRecoilState(modalState); + const { resetModalState, setModalState } = useModalStore(); + const handleError = (error: any) => { if (axios.isAxiosError(error)) { return setModalState((prev) => ({ @@ -19,6 +18,7 @@ export function useErrorHandler() { }, })); } + const axiosError: AxiosError = error; const isConnectionRefusedError = error.code?.includes('ERR_NETWORK'); const isUnprocessableEntity = axiosError.response?.status === 422; diff --git a/src/hooks/useLoading.ts b/src/hooks/useLoading.ts index 800ab96..3ab0834 100644 --- a/src/hooks/useLoading.ts +++ b/src/hooks/useLoading.ts @@ -1,19 +1,7 @@ -import { loadingState } from '@/stores/loading'; -import React from 'react'; -import { useRecoilState, useResetRecoilState } from 'recoil'; +import { useLoadingStore } from '@/stores/loading'; export default function useLoading() { - const [loadingValue, setLoadingValue] = useRecoilState(loadingState); - const resetLoading = useResetRecoilState(loadingState); + const { isLoading, setIsLoading } = useLoadingStore(); - const isLoading = loadingValue.isLoading; - const setIsLoading = (value: boolean) => setLoadingValue((prev) => ({ ...prev, isLoading: value })); - - React.useEffect(() => { - return () => { - resetLoading(); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); return { isLoading, setIsLoading }; } diff --git a/src/stores/loading.ts b/src/stores/loading.ts index b0b5ea1..a34ae2d 100644 --- a/src/stores/loading.ts +++ b/src/stores/loading.ts @@ -1,13 +1,11 @@ -import { atom } from 'recoil'; -import recoilKeys from '@/constants/recoilKeys'; +import { create } from 'zustand'; -interface ILoadingState { +interface ILoadingStore { isLoading: boolean; + setIsLoading: (isShow: boolean) => void; } -export const loadingState = atom({ - key: recoilKeys.LOADING, - default: { - isLoading: false, - }, -}); +export const useLoadingStore = create((set) => ({ + isLoading: false, + setIsLoading: (isShow: boolean) => set((state) => ({ ...state, isLoading: isShow })), +})); diff --git a/src/stores/modal.ts b/src/stores/modal.ts index 7761ad1..b0e4b0e 100644 --- a/src/stores/modal.ts +++ b/src/stores/modal.ts @@ -1,5 +1,4 @@ -import recoilKeys from '@/constants/recoilKeys'; -import { atom, selector } from 'recoil'; +import { create } from 'zustand'; interface IModalState { type?: 'ALERT' | 'CONFIRM'; @@ -11,31 +10,26 @@ interface IModalState { onClickCancel: (...args: any) => void; } -export const modalState = atom({ - key: recoilKeys.MODAL, - default: { - type: undefined, - titleText: undefined, - descText: undefined, - confirmButtonText: '', - cancelButtonText: '', - onClickConfirm: () => {}, - onClickCancel: () => {}, - }, -}); +interface IModalStore { + modalState: IModalState; + setModalState: (fn: (prev: IModalState) => IModalState) => void; + resetModalState: () => void; +} + +const initModalState = { + type: undefined, + titleText: undefined, + descText: undefined, + confirmButtonText: '', + cancelButtonText: '', + onClickConfirm: () => {}, + onClickCancel: () => {}, +}; -export const modalWithText = selector({ - key: recoilKeys.MODAL_WITH_TEXT, - get: ({ get }) => { - const getModalState = get(modalState); - return { - type: getModalState.type, - titleText: getModalState.titleText, - descText: getModalState.descText, - confirmButtonText: getModalState.confirmButtonText, - cancelButtonText: getModalState.cancelButtonText, - onClickConfirm: getModalState.onClickConfirm, - onClickCancel: getModalState.onClickCancel, - }; +export const useModalStore = create((set) => ({ + modalState: initModalState, + setModalState: (fn: (prev: IModalState) => IModalState) => { + set((state) => ({ modalState: fn(state.modalState) })); }, -}); + resetModalState: () => set((prev) => ({ ...prev, modalState: initModalState })), +}));