Firebase uid 로 리덕스 저장해서 해당user의 데이터 불러오기

2024. 12. 11. 22:36·⚛️ React

 

아직도 이해가........잘... 그래서 한번 하나하나 이해해보자...

 

로그인을 하면 생성되는 ? uid를 리덕스 스토어에 저장을 하고, 

해당 유저의 급여내역을 보여주는데, 리덕스에 저장되어있던 uid로 조회를 한다.

 

그리고 유저는 ex) 12월의 급여내역을 보면서 해당 월의 정정신청을 할 수 있는데, 

정정신청 내용을 파이어베이스로 보내야하고, 

정정 신청 내역에서 요청한 내역들을 불러와서 조회할 수 있어야한다. 

 

loginAuthSlice 

const initialState: LoginState = {
	uid: null,
	isLogined: false,
};

const loginAuthSlice = createSlice({
	name: "loginAuth",
	initialState,
	reducers: {
		setUid: (state, action: PayloadAction<{ userId: string } | null>) => {
			state.uid = action.payload;
		},
		clearUid: (state) => {
			state.uid = null;
		},
		setIsLogined: (state, action: PayloadAction<boolean>) => {
			state.isLogined = action.payload;
		},
	},
});
export const { setUid, clearUid, setIsLogined } = loginAuthSlice.actions;
export default loginAuthSlice.reducer;

 

  • 역할:
    • Redux를 통해 사용자의 로그인 상태와 UID를 관리합니다.
    • uid와 isLogined 속성으로 사용자의 인증 정보를 저장합니다.
  • 주요 메서드:
    • setUid: 사용자 UID를 저장합니다.
    • clearUid: 사용자 UID를 초기화합니다.
    • setIsLogined: 사용자가 로그인 상태인지 관리합니다.
  • 연관성:
    • listenAuthChanges에서 setUid를 호출해 사용자의 UID를 저장합니다.
    • 다른 컴포넌트에서 useSelector를 사용하여 UID를 가져옵니다
더보기

여기서 Slice와 reducer는??

슬라이스 :  

- 리덕스 상태(state)를 기능별로 분리한 작은 단위

- 상태(initial state)와 해당 상태를 변경하는 리듀서 함수들로 이루어짐

리듀서 : 

- 이전상태와 액션을 입력받아 새로운 상태를 반환하는 순수함수

- 상태 업데이트 로직의 정의 

 

 

  • 슬라이스는 상태와 리듀서를 묶은 "기능 단위 모듈".
  • 리듀서는 상태를 업데이트하는 순수 함수로, 슬라이스 내부에서 상태 변경 로직을 담당.

 

 

 

ListenAuthChanges

export const listenAuthChanges = async (dispatch: AppDispatch) => {
	dispatch(setLoading(true));

	onAuthStateChanged(auth, async (user) => {
		if (user) {
			try {
				dispatch(setUid({ userId: user.uid }));
				const userData = await getUserData(user.uid);
				if (userData) {
					dispatch(
						setUserInfo({
							email: userData.email,
							name: userData.name,
							team: userData.team,
							grade: userData.grade,
							photoURL: userData.photoURL,
						}),
					);
				} else {
					dispatch(setUserInfo(null));
				}
			} catch (error) {
				console.error("Error fetching user data:", error);
				dispatch(setUserInfo(null));
			}
		} else {
			dispatch(setUserInfo(null));
			dispatch(setUid(null));
		}
	});

	dispatch(setLoading(false));
};

 

 

  • 역할:
    • Firebase의 인증 상태 변화를 감지하고 사용자 정보를 가져옵니다.
  • 작동 방식:
    • onAuthStateChanged를 통해 로그인/로그아웃 상태를 감지합니다.
    • setUid로 UID를 저장하고, setUserInfo로 사용자 정보를 Redux 상태에 저장합니다.
  • 연관성:
    • loginAuthSlice와 userInfoSlice를 통해 상태를 관리합니다.
    • getUserData를 호출해 Firestore에서 사용자 데이터를 가져옵니다.

 

더보기

Dispatch가 뭐냐면

- 리덕스 action을 store에 보냄.

- 상태를 업뎃 하거나 특정 비동기 작업이 완료된 후 스토어에 상태변경 요청을 전달한다.

 

그러면 Action은?

 - 상태를 변경할 때 사용, 

 

비동기 작업과 Thunk 미들웨어...?????

 

(동기: 동시에 일어남, 비동기: 동시에 x, 독립적으로)

 

- 비동기작업처리 : 리덕스는 원래 동기적이지만,(), redux-thunk 같은 미들웨어를 사용하면 비동기작업 처리가능

 

- dispatch내 비동기 함수사용 :async 작성하고 그안에서 여러번 dispatch 호출해 상태 업뎃

src/services/SalaryService

export const getSalaryAmount = async (userId: string, salaryId: string) => {
	const salaryRef = doc(db, `user/${userId}/salaries/${salaryId}`);
	const salarySnap = await getDoc(salaryRef);
	return salarySnap.exists() ? salarySnap.data().actualPayment || 0 : 0;
};

export const saveSalaryCorrection = async ({
	uid,
	salaryId,
	correctionData,
}: {
	uid: any;
	salaryId: string;
	correctionData: {
		type: string;
		reason: string;
		amount: number;
		status: string;
	};
}) => {
	const correctionRef = doc(
		db,
		`user/${uid}/salaries/${salaryId}/salaryCorrection/${salaryId}`,
	);
	await setDoc(correctionRef, {
		...correctionData,
		createdAt: Timestamp.now(),
	});
};

 

 

  • 역할:
    • Firestore 데이터베이스와 상호작용하여 급여 데이터를 가져오거나 수정 요청을 저장합니다.
  • 주요 함수:
    • getSalaryAmount: 특정 사용자의 급여 금액을 반환합니다.
    • saveSalaryCorrection: 정정 요청 데이터를 Firestore에 저장합니다.
  • 연관성:
    • CorrectionModal에서 호출하여 정정 요청 데이터를 저장합니다.
    • SalaryPage에서 Firestore 데이터를 가져옵니다.

getSalaryAmount

export const getSalaryAmount = async (userId: string, salaryId: string) => {
	const salaryRef = doc(db, `user/${userId}/salaries/${salaryId}`);
	const salarySnap = await getDoc(salaryRef);
	return salarySnap.exists() ? salarySnap.data().actualPayment || 0 : 0;
};

 여기서 보면

특정 사용자(userId)의 특정 급여(salaryId)를 가져오고, actualPayment 필드를 반환한다.

 

  • 특정 경로(user/{userId}/salaries/{salaryId})에 있는 문서 참조를 가져옴.
  • 문서를 읽기 위해 getDoc 호출.
  • 문서가 존재하면 데이터(data())에서 actualPayment를 반환, 없으면 0 반환.

 

saveSalaryCorrection

 {
	const correctionRef = doc(
		db,
		`user/${uid}/salaries/${salaryId}/salaryCorrection/${salaryId}`,
	);
	await setDoc(correctionRef, {
		...correctionData,
		createdAt: Timestamp.now(),
	});
};
  • 목적: 특정 사용자(uid)의 특정 급여(salaryId)에 대해 수정 데이터를 저장.
  • 동작:
    1. 경로(user/{uid}/salaries/{salaryId}/salaryCorrection/{salaryId})에 문서 참조 생성.
    2. setDoc으로 수정 데이터를 해당 문서에 저장.
    3. 수정 데이터에 현재 타임스탬프(createdAt) 포함.

SalaryPage

const SalaryPage = () => {
	// `selectedDate`: 선택된 날짜를 상태로 관리 (기본값은 현재 날짜)
	const [selectedDate, setSelectedDate] = useState<Date>(new Date());
	const [salaryData, setSalaryData] = useState<SalaryData | null>(null);

	// 비동기 함수 `fetchUserData`를 생성하여 선택된 날짜의 급여 데이터를 가져옴
	const fetchUserData = useCallback(async (date: Date) => {
		// 선택된 날짜에서 연도와 월을 추출
		const year = date.getFullYear(); // 연도 (예: 2024)
		const month = String(date.getMonth() + 1).padStart(2, "0"); // 월 (1월 = 0이므로 +1 필요, 예: 01, 02)

		const salaryDocPath = `user/${uid}/salaries/salaries_${year}_${month}`;
		// 위 경로에 해당하는 특정 문서 참조 생성
		const salaryDocRef = doc(db, salaryDocPath);

		// 파이어스토어에서 문서 데이터 가져오기
		const salaryDocSnap = await getDoc(salaryDocRef);

		// 문서가 존재하는지 확인
		if (salaryDocSnap.exists()) {
			// 문서가 존재할 경우, 데이터를 `SalaryData` 형식으로 변환하여 상태에 저장
			setSalaryData(salaryDocSnap.data() as SalaryData);
		} else {
			// 문서가 존재하지 않을 경우, 데이터를 업데이트하지 않음 (현재는 처리 로직 없음)
			console.log("Salary document does not exist.");
		}
	}, [uid]); // `useCallback`의 의존성 배열: `uid` 값이 변경될 때만 함수가 새로 생성됨
};

 

 

useCallback을 안 쓰면 무한 요청이 발생하는 이유

useCallback의 역할

  • useCallback은 메모이제이션된 콜백 함수를 반환하여, 의존성 배열이 변경되지 않는 한 함수가 재생성되지 않음.
  • 의존성 배열(예: [uid])에 있는 값이 변경될 때만 새로 함수가 생성됨.

무한 요청이 발생하는 이유

useCallback을 사용하지 않으면 fetchUserData 함수가 매번 새로 생성되고, React의 렌더링 최적화와 관련된 훅에서 반복적으로 호출될 가능성이 높습니다.

일반적인 시나리오:

  1. 컴포넌트가 렌더링될 때마다 새로운 fetchUserData 함수가 생성됨.
  2. 이 함수가 다시 렌더링의 원인이 되는 훅(예: useEffect)에서 호출되면, 다시 렌더링 발생.
  3. 렌더링 → 함수 재생성 → 호출 → 렌더링의 무한 루프.

예시 (무한 루프 발생 가능 코드):

useEffect(() => {
		if (uid && selectedDate) {
			fetchUserData(selectedDate);
		}
	}, [uid, selectedDate, fetchUserData]);

이 useEffect는 특정 상태(uid, selectedDate)가 변경될 때마다 **fetchUserData**를 호출하여 데이터를 가져오는 역할

 

위 코드 문제점:

  • fetchUserData가 매번 새로운 참조를 가지기 때문에, useEffect는 이를 의존성 변화로 간주하고 다시 실행.
  • 결과적으로 컴포넌트가 계속 렌더링 → 함수 재생성 → 훅 재실행 → 무한 루프.

useCallback으로 해결:

  • useCallback을 사용하면 fetchUserData는 동일한 참조를 유지하여, useEffect가 재실행되지 않음.
const fetchUserData = useCallback(async (date: Date) => {
    // 비동기 데이터 로직
}, [uid]); // uid가 변경될 때만 함수가 재생성됨.

useEffect(() => {
    fetchUserData(selectedDate); // 의존성 배열이 안정적이므로 무한 루프 방지.
}, [selectedDate, fetchUserData]);

CorrectionModal

const CorrectionModal = ({
	isOpen,
	onClose,
	selectedDate,
}: {
	isOpen: boolean;
	onClose: () => void;
	selectedDate: Date;
}) => {
	const getSalaryId = () => {
		const year = selectedDate.getFullYear();
		const month = String(selectedDate.getMonth() + 1).padStart(2, "0");
		return `salaries_${year}_${month}`;
	};

	const handleApply = async () => {
		const salaryId = getSalaryId();
		const amount = await getSalaryAmount(uid, salaryId);
		await saveSalaryCorrection({
			uid,
			salaryId,
			correctionData: { type, reason, amount, status: "검토 중" },
		});
	};
};

 

  • 역할:
    • 정정 요청을 입력받아 Firestore에 저장합니다.
    • 선택된 날짜(selectedDate)를 기준으로 급여 데이터를 가져옵니다.
  • 연관성:
    • SalaryPage에서 호출되어 모달을 표시합니다.
    • SalaryService를 호출하여 정정 데이터를 저장합니다.

 

 

그래서 정리해보자면, ,, ,. . . .

1. 로그인하면 로그인정보가 리덕스에 저장되고, 이걸통해 uid를 리덕스에 가져올수있다.

2. 사용자가 정정 요청 모달을 열면 useEffect로 초기상태 설정하고, 입력해서 apply함수로 요청보낸다.

3. uid 랑 reason이 제대로 있는지 if문으로 확인하고

4. 해당 월 페이지에서 정정요청을 보내는거니까, 선택한 날짜(selectedDate)를 기준으로 SalaryId를 생성하고

5. firebase에서 getSalaryAmount인 실지급액 가져오는거 호출해서 SalaryId에 저장된 amount 가져온다.

6. saveSalaryCorrection호출해서 정정요청을 firebase에 저장한다. 작성내용에 + 금액, 기본상태(검토중)

 

 

파일 간의 흐름

  1. 로그인 인증 상태 관리:
    • loginAuthSlice에서 UID를 저장합니다.
    • listenAuthChanges로 Firebase의 인증 변화를 감지하고 상태를 업데이트합니다.
  2. 급여 데이터 가져오기:
    • SalaryPage에서 SalaryService를 호출하여 Firestore의 데이터를 불러옵니다.
  3. 정정 요청 처리:
    • CorrectionModal에서 사용자의 입력을 받아 Firestore에 저장합니다.
    • 저장 요청은 SalaryService의 saveSalaryCorrection를 통해 처리됩니다.
  4. Redux 상태와의 연계:
    • Redux를 통해 UID와 사용자 정보를 저장하고 컴포넌트에서 필요시 불러옵니다

 

아아 복잡해랑

 

 

 

 

 

'⚛️ React' 카테고리의 다른 글

이미지 업로드 구현( 미리보기)  (2) 2024.12.22
[ Context API ] React Context , redux와의 차이점  (1) 2024.12.18
firebase + redux 로 상태관리 하기  (0) 2024.12.10
드롭박스를 만들어보자.. +외부클릭으로 드롭다운 닫히는 동작  (0) 2024.11.28
React-Portal로 Modal 구현하기  (0) 2024.11.27
'⚛️ React' 카테고리의 다른 글
  • 이미지 업로드 구현( 미리보기)
  • [ Context API ] React Context , redux와의 차이점
  • firebase + redux 로 상태관리 하기
  • 드롭박스를 만들어보자.. +외부클릭으로 드롭다운 닫히는 동작
gprorogpfus
gprorogpfus
:- >
  • gprorogpfus
    gpfusdldks
    gprorogpfus
  • 전체
    오늘
    어제
    • 분류 전체보기 (55)
      • 🎭JavaScript (2)
      • 👚 CSS (1)
      • ⚛️ React (13)
      • 🌃 Next.js (5)
        • 🔜 next.js-study (3)
      • 🥏TypeScript (10)
      • 🏴알고리즘 (2)
      • 🌴트러블슈팅 (3)
      • ⛲ 프로젝트 (6)
        • 👖gproro-shop-app (8)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
    • 글쓰기
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    TypeScript
    JavaScript
    Redux
    react
    GIT
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.2
gprorogpfus
Firebase uid 로 리덕스 저장해서 해당user의 데이터 불러오기
상단으로

티스토리툴바