Skip to content
Open
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
8ec1008
install: recoil 설치
INSANE-P May 30, 2025
ed1ebc8
refactor: Context API에서 Recoil 방식으로 전환
INSANE-P May 30, 2025
b8ed211
refactor: 불필요한 핸들러 함수 제거
INSANE-P May 30, 2025
fa5fa6d
refactor: 불필요한 내부 함수 선언 제거
INSANE-P Jun 2, 2025
e502aee
refactor: 데이터 fech 에러 처리 방식 추가 및 상태 refresh로직 추가
INSANE-P Jun 2, 2025
342ed3b
refactor: recoil 상태 관리 폴더명 변경(recoil->store)
INSANE-P Jun 2, 2025
36e5a31
refactor: recoil -> redux toolkit 기반의 상태 관리로 마이그레이션
INSANE-P Jul 4, 2025
5e00a3c
Merge branch 'INSANE-P' into STEP2-3
INSANE-P Jul 4, 2025
d8222c2
refactor: 리뷰 반영하여 POST 요청도 Redux를 통해 관리 POST 실패, 로딩 상태 관리 추가
INSANE-P Jul 8, 2025
466fbb4
충돌 해결
INSANE-P Jul 8, 2025
d895e69
feat: modalSlice 추가 (모달 상태 모듈화)
INSANE-P Oct 4, 2025
fd7f46a
feat: restaurantSlice 추가 (식당 상태 모듈화)
INSANE-P Oct 4, 2025
a49804a
feat: appstore에 slice를 통해 store 구현/상태와 액션 훅 구현
INSANE-P Oct 4, 2025
08183c0
feat: 서버 중복 호출 방지 로직 추가
INSANE-P Oct 4, 2025
79a074b
feat: RTK 방식의 상태 구현에서 zustand의 스토어를 활용한 상태 구현으로 컴포넌트 마이그레이션
INSANE-P Oct 4, 2025
7fee236
chore: Redux/Recoil 관련 파일 삭제
INSANE-P Oct 4, 2025
1a6e02b
chore: zustand 의존성 추가, Redux/Recoil 의존성 제거
INSANE-P Oct 4, 2025
4ba62ed
feat: selectedCategory를 sessionStorage에 저장하여 새로고침에 의한 초기화 방지 (persist)
INSANE-P Oct 4, 2025
5231c7d
docs: 2-2 미션 Recoil → Zustand로 수정
INSANE-P Oct 4, 2025
d72b0c3
chore: RTK의 불필요한 store.js 파일 삭제
INSANE-P Oct 4, 2025
20e2d9f
Merge branch 'INSANE-P' into STEP2-2(Zustand)
INSANE-P Oct 4, 2025
2e3b09a
docs: README 전역상태 텍스트 수정(Recoil -> Zustand)
INSANE-P Oct 5, 2025
b4ca1b8
Merge branch 'STEP2-2(Zustand)' of https://github.com/INSANE-P/self-p…
INSANE-P Oct 5, 2025
4d13c17
refactor: Header의 action 함수 로직 전환
INSANE-P Oct 25, 2025
2253d7f
refactor: Category의 action 함수의 방식을 zustand로 마이그레이션
INSANE-P Oct 25, 2025
f4146ea
chore: 사용하지 않는 RTK 파일 제거
INSANE-P Oct 25, 2025
8446b52
docs: README 파일 수정
INSANE-P Oct 28, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions Let's go further/02-state-management-tools/2.2- Zustand/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# 02-2. 전역상태관리 - Zustand

## 🎯 요구사항

- Context API로 구성한 애플리케이션을 Zustand 기반 전역 상태로 마이그레이션하세요.
- (선택): 카테고리 필터의 선택된 카테고리가 새로고침 후에도 유지되도록 구현해보세요.
- props에 대한 요구사항은 2-1 요구사항과 같습니다.
- Zustand를 **** 사용하는지, Context API와 비교했을때 어떤 점이 달랐는지, 또 trade-off가 있는지 적어주세요.
- 기술적인 것도 좋고 개발자의 경험 측면에서도 좋습니다.

### 😗구현 예시

- 컴포넌트의 이름이나 구조는 마음대로 변경해도 좋습니다.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이거 제가 문서를 만들 때 처음 썼던 문구인 거 같은데 "마음대로 변경해도 좋다" 라는게 마치 막지어도 된다는 느낌을 줄 수 있지도 않을까?라는 걱정이 드네요😢

Suggested change
- 컴포넌트의 이름이나 구조는 마음대로 변경해도 좋습니다.
- 컴포넌트의 이름이나 구조를 정한 이유가 명확해야하며 타인에게 설명할 수 있어야합니다.

위의 제안처럼 하는 것이 어떤지 검토해주시면 감사하겠읍니다 😄 02-state-management에 모든 md파일에 적용해주시면 더 좋을 것 같아요!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좋은 것 같습니다. 수정하겠습니다.🪄

- 아래 코드는 Zustand 스토어를 설정하는 예시입니다.

```javascript
import { create } from 'zustand';

// Zustand 스토어 예시
export const useBear = create((set) => ({
// state
bears: 0,

// actions
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
removeAllBears: () => set({ bears: 0 }),
updateBears: (newBears) => set({ bears: newBears }),
}));
```

## ✅ 키워드

- props drilling
- 전역상태관리
- Zustand
- create
- selector pattern
- set / get
- slice pattern
- middleware (persist, createJSONStorage)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이것들을 빼는 건 어떤가요? Zustand에 대해 너무 많은 것을 명시하여 제공해주는 것은 오히려 독이 될거 같다는 의견인데 찬빈님은 어떻게 생각하시나요?

Copy link
Author

@INSANE-P INSANE-P Oct 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

네 너무 많은 내용을 명시하여 스스로 학습해보는 과정을 뺏길 수 있을 것 같습니다. 아래와 같이 기본적인 키워드로 수정하겠습니다.

##  키워드

- props drilling
- 전역상태관리
  - Zustand
  - create
  - set / get


## 🧙‍♀️ 진행 가이드

- 진행시간 : 1시간 내에 완료하는 것을 목표로 합니다.

## 🔗 참고 문서

- [Zustand 공식문서](https://recoiljs.org/docs/introduction/installation/)
- [Middleware: persist 관련 문서](https://zustand.docs.pmnd.rs/integrations/persisting-store-data)
36 changes: 0 additions & 36 deletions Let's go further/02-state-management-tools/2.2-Recoil/README.md

This file was deleted.

15 changes: 11 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,30 @@
# 🛣️Let's Go Further Together...

###### **이 Repository는 cho-log : self-paced-react step5까지 마치고 난 후 더 나아가 고도화하기 위해 존재합니다.**

###### **미션 README 문서는 Let's go further디렉토리에 존재합니다.**

> 🍀Cho-log-study : self-paced-react, React 기초
>
> https://github.com/cho-log/self-paced-react
# Why❓

아래의 새로운 기술을 쓸 때는

1. 왜 이 기술을 쓰는지? <br/>
2. 기존의 것과 어떤 장단점이 있는지? <br/>
3. 썼을때 어떤 일이 일어났는지? <br/>
등의 **"왜?"** 라는 질문에 답할 수 있어야합니다.<br/>
2. 기존의 것과 어떤 장단점이 있는지? <br/>
3. 썼을때 어떤 일이 일어났는지? <br/>
등의 **"왜?"** 라는 질문에 답할 수 있어야합니다.<br/>

고도화 작업을 할때 항상 **"왜?"** 라는 질문에 답하며 **Deep Dive**합시다.

# 📈Something To Learn More About

### 1. styled-component 적용

### 2. 전역상태관리 툴 다루기

1. #### Context API
2. #### Recoil
2. #### Zustand
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯

3. #### Redux
82 changes: 35 additions & 47 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 2 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,8 @@
"@reduxjs/toolkit": "^2.8.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-redux": "^9.2.0",
"recoil": "^0.7.7",
"redux": "^5.0.1",

"styled-components": "^6.1.18"
"styled-components": "^6.1.18",
"zustand": "^5.0.8"
},
"devDependencies": {
"@eslint/js": "^9.9.0",
Expand Down
11 changes: 4 additions & 7 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
import { useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { fetchRestaurants } from './features/restaurantSlice';
import Header from './components/header/Header';
import CategoryFilter from './components/main/CategoryFilter';
import RestaurantList from './components/main/RestaurantList';
import RestaurantDetailModal from './components/aside/RestaurantDetailModal';
import AddRestaurantModal from './components/aside/AddRestaurantModal';
import StyleProvider from './styles/StyleProvider';

import { useRestaurantActions } from './store/appStore';

const App = () => {
const dispatch = useDispatch();
const { fetchRestaurants } = useRestaurantActions();

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

return (
<StyleProvider>
Expand All @@ -28,7 +26,6 @@ const App = () => {
<AddRestaurantModal />
</aside>
</StyleProvider>

);
};

Expand Down
34 changes: 16 additions & 18 deletions src/components/aside/AddRestaurantModal.jsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import styled from 'styled-components';
import { selectableCategories } from '../../constant/constant';
import Modal from './modal/Modal';
import { useSelector, useDispatch } from 'react-redux';
import {
fetchRestaurants,
postNewRestaurant,
} from '../../features/restaurantSlice';
import { closeRestaurantAddModal } from '../../features/modalSlice';
useIsAddModalOpen,
usePostStatus,
useRestaurantActions,
useModalActions,
useAppStore,
} from '../../store/appStore';

const AddRestaurantForm = styled.form``;

Expand Down Expand Up @@ -81,13 +82,12 @@ const SubmitButton = styled.button`
`;

const AddRestaurantModal = () => {
const dispatch = useDispatch();
const handleCloseRestaurantAddModal = () =>
dispatch(closeRestaurantAddModal());
const isRestaurantAddModalOpen = useSelector(
(state) => state.modal.isRestaurantAddModalOpen
);
const postStatus = useSelector((state) => state.restaurant.postStatus);
const isRestaurantAddModalOpen = useIsAddModalOpen();
const postStatus = usePostStatus();
const { postNewRestaurant, fetchRestaurants } = useRestaurantActions();
const { closeRestaurantAddModal } = useModalActions();

const handleCloseRestaurantAddModal = () => closeRestaurantAddModal();

const handleFormSubmit = async (e) => {
e.preventDefault();
Expand All @@ -97,14 +97,12 @@ const AddRestaurantModal = () => {
name: e.target.name.value,
description: e.target.description.value,
};
const resultAction = await dispatch(postNewRestaurant(newRestaurant));

if (postNewRestaurant.rejected.match(resultAction)) {
alert(`추가 실패 ERROR: ${resultAction.error.message}`);
await postNewRestaurant(newRestaurant);
if (useAppStore.getState().postStatus === 'failed') {
alert('식당 추가에 실패 했습니다.');
return;
}

await dispatch(fetchRestaurants());
await fetchRestaurants();
handleCloseRestaurantAddModal();
e.target.reset();
};
Expand Down
26 changes: 12 additions & 14 deletions src/components/aside/RestaurantDetailModal.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import styled from 'styled-components';
import Modal from './modal/Modal';
import { useSelector, useDispatch } from 'react-redux';
import { closeRestaurantDetailModal } from '../../features/modalSlice';
import { setSelectedRestaurant } from '../../features/restaurantSlice';

import {
useIsDetailModalOpen,
useSelectedRestaurant,
useRestaurantActions,
useModalActions,
} from '../../store/appStore';

const RestaurantInfo = styled.div`
margin-bottom: 24px;
Expand Down Expand Up @@ -35,16 +37,13 @@ const CloseButton = styled.button`
`;

const RestaurantDetailModal = () => {
const dispatch = useDispatch();
const isRestaurantDetailModalOpen = useSelector(
(state) => state.modal.isRestaurantDetailModalOpen
);
const selectedRestaurant = useSelector(
(state) => state.restaurant.selectedRestaurant
);
const isRestaurantDetailModalOpen = useIsDetailModalOpen();
const selectedRestaurant = useSelectedRestaurant();
const { setSelectedRestaurant } = useRestaurantActions();
const { closeRestaurantDetailModal } = useModalActions();
const handleCloseRestaurantDetailModal = () => {
dispatch(closeRestaurantDetailModal());
dispatch(setSelectedRestaurant(null));
closeRestaurantDetailModal();
setSelectedRestaurant(null);
};
return (
<Modal
Expand All @@ -59,7 +58,6 @@ const RestaurantDetailModal = () => {
</RestaurantInfo>
<CloseButtonContainer>
<CloseButton type="button" onClick={handleCloseRestaurantDetailModal}>

닫기
</CloseButton>
</CloseButtonContainer>
Expand Down
Loading