일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- 코딩부트캠프
- 삼항연산자
- 날씨 웹 만들기
- 자바스크립트
- 코드스테이츠
- 자바스크립트 reduce()
- 프론트개발
- 엘리스 AI 트랙 5기
- 자바스크립트 날씨
- HTML
- [파이썬 실습] 심화 문제
- reactnativecli
- [AI 5기] 연습 문제집
- 자바스크립트 날씨 웹 만들기
- 리트코드
- 엘리스
- leetcode
- RN 프로젝트
- [파이썬 실습] 기초 문제
- 자바스크립트 split()
- 개발공부
- [파이썬 실습] 중급 문제
- 프론트개발공부
- JavaScript
- 간단한 날씨 웹 만들기
- 엘리스 ai 트랙
- 부트캠프
- 개발일기
- 자바스크립트 sort()
- 프로그래머스
- Today
- Total
개발조각
[React-native CLI] RN에서 crud구현(recoil, realm) 본문
[React-native CLI] RN에서 crud구현(recoil, realm)
개발조각 2024. 3. 22. 15:22드디어 오류의 오류를 거쳐 프로젝트에서 crud를 구현했습니다.
RN에서 crud를 구현한 부분에 대해 공유해보려 합니다.
recoil을 사용하게 된 이유
분명 realm에서는 crud가 잘 작동되는데, 화면에서는 realm이 crud가 돼도 업데이트가 되지 않는 오류를 발견하게 되었습니다.
이 화면에서 추가, 수정, 삭제를 해도 변화가 없는 문제 발생 -> 추가, 수정, 삭제를 해도 화면이 업데이트를 안 함
- 추가를 하면 달력에 mark가 찍혀야 되며, 운행정보 화면이 나와야 된다.
- 수정을 하면 운행정보 내용이 업데이트되어야 된다.
- 삭제를 하면 달력에 mark가 사라지고, 운행정보가 없습니다. 화면이 나와야 된다.
realm에서 데이터를 확인하면 realm에서는 추가, 업데이트, 삭제 시 데이터가 실시간으로 업데이트가 잘되고 있다는 것을 확인했고,
realm에서 데이터가 업데이트 잘 되니까 이 값을 가지고 useEffect에 의존성 배열에 넣어보면 되겠네~ 했더니 전혀 해결이 되지 않았습니다.
결국 realm에서 데이터를 아무리 업데이트돼도 화면변화에는 아무런 효과가 없다는 판단이 들어 다른 방안을 생각해 보았습니다.
그래서 사용한 해결방안이 상태관리를 사용해 보자!!! 이렇게 되었습니다.
- recoil로 recordState라는 배열을 만들고 realm Read데이터를 recordState를 넣어주고,
- 추가, 수정, 삭제 시 realm 데이터를 recordState에 넣어줘서,
- recordState값이 변화될 때마다 useEffect를 이용하여 화면을 업데이트해 주었습니다.
recoil 설치
npm install recoil
atom 세팅
recoil/atoms.ts
// library
import {atom} from 'recoil';
// types
import {RecordType} from '../types/types';
export const recordState = atom<RecordType[]>({
key: 'recordState',
default: [],
});
여기서 atom에 타입지정을 꼭 해주어야 됩니다.
안 하면 atom에 값을 넣어줄 때 타입에러가 발생합니다.
App에 RecoilRoot 세팅하기
// react, react-native
import React from 'react';
import {NavigationContainer, DefaultTheme} from '@react-navigation/native';
import StackNavigator from './navigation/StackNavigator';
import {RecoilRoot} from 'recoil';
const App = () => {
// 네비게이션 테마
const customTheme = {
...DefaultTheme,
colors: {
...DefaultTheme.colors,
background: '#fff',
},
};
return (
<RecoilRoot>
<NavigationContainer theme={customTheme}>
<StackNavigator />
</NavigationContainer>
</RecoilRoot>
);
};
export default App;
저는 여기서 react-navigation을 쓰고 있어 이렇게 설정해 주었습니다.
여기서 realm데이터를 recoil에 담아주고 싶었지만 여기서 하면 자꾸 오류가 나서 home.tsx에다가 해주었습니다.
realm설정
schema.ts
// 운행기록 스키마
export const RecordSchema = {
name: 'Record',
properties: {
date: 'string',
card: 'int',
cash: 'int', // 현금
lpgInjectionVolume: 'int', // LPG 주입량
lpgUnitPrice: 'int', // LPG 단가
mileage: 'int', // 주행거리
businessDistance: 'int', // 영업거리
toll: 'int', // 통행료
operatingAmount: 'int', // 영업금액
lpgChargeAmount: 'int', // LPG 충전 금액
fuelEfficiency: 'int', // 연비
lpgUsage: 'int', // LPG 사용량
},
primaryKey: 'date',
};
recordRealmFunctions.ts
// library
import Realm from 'realm';
// realm, type, recoil
import {RecordSchema} from './schema';
import {RecordType} from '../types/types';
// Realm 데이터베이스 연결
const realm = new Realm({schema: [RecordSchema]});
// CREATE : 새로운 기록 추가
export const createRecord = (newData: RecordType) => {
realm.write(() => {
realm.create('Record', newData);
});
console.log('데이터가 생성되었습니다.');
};
// READ_All
export const readAllRecord = () => {
const data = realm.objects<RecordType>('Record');
return data;
};
// READ : 선택한 날짜의 데이터
export const readSelectDateRecord = (selectDate: string) => {
const selectDateData = realm
.objects('Record')
.filtered(`date = '${selectDate}'`)[0];
return selectDateData;
};
// UPDATE
export const updateRecord = (newData: RecordType) => {
const selectDateData = realm
.objects('Record')
.filtered(`date = '${newData.date}'`)[0];
if (selectDateData) {
realm.write(() => {
selectDateData.card = newData.card;
selectDateData.cash = newData.cash;
selectDateData.lpgInjectionVolume = newData.lpgInjectionVolume;
selectDateData.lpgUnitPrice = newData.lpgUnitPrice;
selectDateData.mileage = newData.mileage;
selectDateData.businessDistance = newData.businessDistance;
selectDateData.toll = newData.toll;
selectDateData.operatingAmount = newData.operatingAmount;
selectDateData.lpgChargeAmount = newData.lpgChargeAmount;
selectDateData.fuelEfficiency = newData.fuelEfficiency;
selectDateData.lpgUsage = newData.lpgUsage;
});
console.log('데이터가 수정되었습니다.');
}
};
// DELETE
export const deleteRecord = (selectDate: string) => {
const selectDateData = realm
.objects('Record')
.filtered(`date = '${selectDate}'`)[0];
realm.write(() => {
realm.delete(selectDateData);
});
console.log(`${selectDate} 데이터가 삭제되었습니다.`);
};
Home 스크린에 realm데이터를 recoil에 담기
screens/Home.tsx
// react, react-native
import React, {useEffect, useState} from 'react';
import {ScrollView} from 'react-native';
// library
import dayjs from 'dayjs';
// assets, utils, realm
import {useSetRecoilState} from 'recoil';
import {recordState} from '../recoil/atoms.ts';
import {readAllRecord} from '../realm/recordRealmFunctions.ts';
// component
import YearBusinessAmount from '../components/home/YearBusinessAmount.tsx';
import MonthCalendar from '../components/home/MonthCalendar.tsx';
import MonthBusinessAmount from '../components/home/MonthBusinessAmount.tsx';
import MonthRecordInfo from '../components/home/MonthRecordInfo.tsx';
import CreateButton from '../components/home/CreateButton.tsx';
// style
import {Home as Style} from '../styles/home.styles.ts';
const Home = () => {
// 현재 날짜: 년-월-일
const currentDate = dayjs().format('YYYY-MM-DD');
const [selectDate, setSelectDate] = useState(currentDate);
const setRecordData = useSetRecoilState(recordState);
useEffect(() => {
const data = readAllRecord();
setRecordData(data.map(record => ({...record})));
}, [setRecordData]);
return (
<Style.container>
<ScrollView>
{/* 연간 영업 금액 */}
<YearBusinessAmount />
{/* 월별 운행 기록 */}
<Style.bottomContainer>
{/* 월별 달력 */}
<MonthCalendar
currentDate={currentDate}
selectDate={selectDate}
setSelectDate={setSelectDate}
/>
{/* 이번달 영업 금액 */}
<MonthBusinessAmount />
{/* 이번달 운행 정보 */}
<MonthRecordInfo />
</Style.bottomContainer>
</ScrollView>
{/* create 버튼 */}
<CreateButton currentDate={currentDate} />
</Style.container>
);
};
export default Home;
주요 코드
const setRecordData = useSetRecoilState(recordState);
useEffect(() => {
const data = readAllRecord();
setRecordData(data.map(record => ({...record})));
}, [setRecordData]);
여기서 setRecordData에 다음과 같이 넣어준 이유는
setRecordData(data.map(record => ({...record})));
이렇게 안 넣어주고 아래와 같은 오류가 발생합니다...
setRecordData([...data]);
TypeError: ownKeys 대상은 확장할 수 없지만 트랩 결과 키가 대상 키와 다릅니다
해결방안
setRecordData(data.map(record => ({...record})));
이렇게 넣어줘야 되는 이유
setRecordData 함수는 Recoil 상태를 업데이트하는 함수입니다.
Recoil 상태를 업데이트할 때는 상태의 이전 값을 변경하지 않고 새로운 값을 전달해야 합니다.
이를 위해 객체나 배열을 새로 생성하여 이전 상태를 변경하지 않고 새로운 상태를 전달해야 합니다.
따라서 data.map(record => ({...record}))를 사용하여 데이터의 각 요소를 복제하고
새로운 배열을 생성한 후, 이 배열을 setRecordData 함수에 전달합니다.
이렇게 하면 Recoil 상태가 변경되고, 해당 변경 내용이 React 컴포넌트에 반영됩니다.
realm에서 데이터 추가, 수정, 삭제할 시
추가, 수정, 삭제할 시에 setRecordData에 업데이트된 Realm데이터를 넣어주었습니다.
새로운 데이터를 저장할 때와 수정할 때를 같은 버튼에 두었기 때문에 추가하기, 수정하기 코드를 같이 적어주었습니다.
components/record/ButtonWrap.tsx
// react, react-native
import React from 'react';
import {useNavigation} from '@react-navigation/native';
// realm, types
import {
createRecord,
readAllRecord,
updateRecord,
} from '../../realm/recordRealmFunctions';
import {RecordType} from '../../types/types';
// library
import {useSetRecoilState} from 'recoil';
// recoil
import {recordState} from '../../recoil/atoms';
// component
import BasicsButton from '../common/BasicsButton';
// style
import {ButtonWrap as Style} from '../../styles/record.styles';
interface PropsType {
record: string;
state: RecordType;
}
const ButtonWrap = ({record, state}: PropsType) => {
const navigation = useNavigation();
const setRecordData = useSetRecoilState(recordState);
const onGoBackPress = () => {
navigation.goBack();
};
const onSavePress = () => {
if (record === 'CREATE') {
createRecord(state);
} else {
updateRecord(state);
}
const data = readAllRecord();
setRecordData(data.map(record => ({...record})));
navigation.goBack();
};
return (
<Style.container>
<BasicsButton text="취소" option="cancel" onButtonPress={onGoBackPress} />
<BasicsButton text="저장" onButtonPress={onSavePress} />
</Style.container>
);
};
export default ButtonWrap;
주요 코드
const onSavePress = () => {
if (record === 'CREATE') {
createRecord(state);
} else {
updateRecord(state);
}
const data = readAllRecord();
setRecordData(data.map(record => ({...record})));
navigation.goBack();
};
components/calendar/DeleteModal.tsx
// react, react-native
import React, {Dispatch, SetStateAction} from 'react';
import {Modal} from 'react-native';
// library
import dayjs from 'dayjs';
import {useSetRecoilState} from 'recoil';
// realm, recoil
import {deleteRecord, readAllRecord} from '../../realm/recordRealmFunctions';
import {recordState} from '../../recoil/atoms';
// component
import BasicsButton from '../common/BasicsButton';
// style
import {Modal as Style} from '../../styles/common.styles';
interface PropsType {
modalVisible: boolean;
setModalVisible: Dispatch<SetStateAction<boolean>>;
selectDate: string;
}
const DeleteModal = ({
modalVisible,
setModalVisible,
selectDate,
}: PropsType) => {
const setRecordData = useSetRecoilState(recordState);
const onCancelPress = () => {
setModalVisible(false);
};
const onDeletePress = () => {
deleteRecord(selectDate);
setModalVisible(false);
const data = readAllRecord();
setRecordData(data.map(record => ({...record})));
};
return (
<Modal
animationType="fade"
transparent={true}
visible={modalVisible}
onRequestClose={() => {
setModalVisible(false);
}}>
<Style.modalWrap>
<Style.container>
<Style.textWrap>
<Style.title>
운행정보 기록
<Style.orangeText>삭제</Style.orangeText>
하기
</Style.title>
<Style.text>
<Style.orangeText>
{dayjs(selectDate).format('YYYY년 M월 D일')}
</Style.orangeText>
운행정보기록을 {'\n'}
삭제하시겠습니까?
</Style.text>
</Style.textWrap>
<Style.buttonWrap>
<BasicsButton
text={'취소'}
option={'cancel'}
onButtonPress={onCancelPress}
/>
<BasicsButton text={'삭제'} onButtonPress={onDeletePress} />
</Style.buttonWrap>
</Style.container>
</Style.modalWrap>
</Modal>
);
};
export default DeleteModal;
주요 코드
const onDeletePress = () => {
deleteRecord(selectDate);
setModalVisible(false);
const data = readAllRecord();
setRecordData(data.map(record => ({...record})));
};
Calendar화면 업데이트
useEffect 의존성 배열에 recordData를 추가하여 recordData가 변결될떄마다 업데이트되게 해 주었습니다.
components/common/Calendar.tsx
// react, react-native
import React, {useCallback, useEffect, useState} from 'react';
// library
import dayjs from 'dayjs';
import {useRecoilValue} from 'recoil';
// realm, recoil
import {readSelectDateRecord} from '../realm/recordRealmFunctions';
import {recordState} from '../recoil/atoms';
// component
import CalendarView from '../components/common/CalendarView';
import RecordInfo from '../components/calendar/RecordInfo';
import ButtonWrap from '../components/calendar/ButtonWrap';
// style
import {Calender as Style} from '../styles/calendar.styles';
import NoneRecordInfo from '../components/calendar/NoneRecordInfo';
// 추가, 삭제, 수정해도 달력페이지에 업데이트 되지 않음
const Calendar = () => {
// 현재 날짜: 년-월-일
const currentDate = dayjs().format('YYYY-MM-DD');
const [selectDate, setSelectDate] = useState(currentDate); // 선택한 날짜
const [isRecord, setIsRecord] = useState(false);
// selectDate가 realm에 있는지 체크
const readDB = useCallback(() => {
const selectDateData = readSelectDateRecord(selectDate);
setIsRecord(selectDateData ? true : false);
}, [selectDate]);
const recordData = useRecoilValue(recordState);
useEffect(() => {
readDB();
}, [readDB, selectDate, recordData]);
return (
<Style.container>
<Style.calendarContainer>
<CalendarView checkDate={selectDate} setCheckDate={setSelectDate} />
</Style.calendarContainer>
<Style.line></Style.line>
{/* 선택한 날짜의 데이터가 있는지 체크 */}
{isRecord ? (
<Style.scrollView>
<RecordInfo selectDate={selectDate} />
<ButtonWrap selectDate={selectDate} />
</Style.scrollView>
) : (
<NoneRecordInfo selectDate={selectDate} />
)}
</Style.container>
);
};
export default Calendar;
주요 코드
useEffect(() => {
readDB();
}, [readDB, selectDate, recordData]);
components/common/CalendarView.tsx
여기서는 realm의 데이터를 직접 업데이트할 필요가 없기 때문에 recoil에 있는 데이터를 사용해주었습니다.
// react, react-native
import React, {
Dispatch,
SetStateAction,
useCallback,
useEffect,
useState,
} from 'react';
import {Calendar, LocaleConfig} from 'react-native-calendars';
import {SvgXml} from 'react-native-svg';
// library
import dayjs from 'dayjs';
import {useRecoilValue} from 'recoil';
// assets, recoil
import {svg} from '../../assets/svg';
import {recordState} from '../../recoil/atoms';
// style
import Theme from '../../styles/Theme';
LocaleConfig.locales['ko'] = {
monthNames: [
'01월',
'02월',
'03월',
'04월',
'05월',
'06월',
'07월',
'08월',
'09월',
'10월',
'11월',
'12월',
],
monthNamesShort: [
'1월',
'2월',
'3월',
'4월',
'5월',
'6월',
'7월',
'8월',
'9월',
'10월',
'11월',
'12월',
],
dayNames: [
'일요일',
'월요일',
'화요일',
'수요일',
'목요일',
'금요일',
'토요일',
],
dayNamesShort: ['일', '월', '화', '수', '목', '금', '토'],
today: '오늘',
};
LocaleConfig.defaultLocale = 'ko';
interface PropsType {
checkDate: string;
setCheckDate: Dispatch<SetStateAction<string>>;
}
const CalendarView = ({checkDate, setCheckDate}: PropsType) => {
// 현재 날짜: 년-월-일
const currentDate = dayjs().format('YYYY-MM-DD');
const recordData = useRecoilValue(recordState);
// realm에서 기록한 날짜 담기
const [markedDate, setMarkedDate] = useState<
Record<string, {marked: boolean}>
>({});
const readMarkedAllDB = useCallback(() => {
const newMarkedDate: Record<string, {marked: true}> = {};
if (recordData) {
recordData.forEach(record => {
if (record.date) {
newMarkedDate[record.date] = {marked: true};
}
});
setMarkedDate({...newMarkedDate});
} else {
console.log('데이터가 없습니다.');
}
}, [recordData]);
useEffect(() => {
readMarkedAllDB();
}, [readMarkedAllDB]);
const markedSelectedDates = {
...markedDate,
[checkDate]: {
selected: true,
marked: markedDate[checkDate]?.marked,
dotColor: Theme.colors.mainDeep,
customStyles: {
container: {
backgroundColor: `${Theme.colors.mainLight}`,
borderRadius: 10,
},
text: {
color: `${Theme.colors.black}`,
},
},
},
};
const customTheme = {
dotColor: '#FF7B00',
// year, month
textSectionTitleColor: `${Theme.colors.black}`,
textSectionTitleDisabledColor: `${Theme.colors.black}`,
// day of week, day
textDayHeaderFontSize: 14,
textDayFontSize: 14,
textDisabledColor: `${Theme.colors.grey}`,
// today
todayTextColor: `${Theme.colors.mainDeep}`,
};
// {"dateString": "2024-03-18", "day": 18, "month": 3, "timestamp": 1710720000000, "year": 2024}
const onDayPress = (day: {dateString: SetStateAction<string>}) => {
setCheckDate(day.dateString);
};
return (
<>
<Calendar
markingType={'custom'}
markedDates={markedSelectedDates}
onDayPress={onDayPress}
maxDate={currentDate}
theme={customTheme}
monthFormat={'yyyy년 M월'}
renderArrow={direction =>
direction === 'left' ? (
<SvgXml xml={svg.prev} />
) : (
<SvgXml xml={svg.next} />
)
}
/>
</>
);
};
export default CalendarView;
주요 코드
const readMarkedAllDB = useCallback(() => {
const newMarkedDate: Record<string, {marked: true}> = {};
if (recordData) {
recordData.forEach(record => {
if (record.date) {
newMarkedDate[record.date] = {marked: true};
}
});
setMarkedDate({...newMarkedDate});
} else {
console.log('데이터가 없습니다.');
}
}, [recordData]);
useEffect(() => {
readMarkedAllDB();
}, [readMarkedAllDB]);
'React-Native > [프로젝트] 택시 운행관리 기록장' 카테고리의 다른 글
[React-native CLI] RN에서 그래프 라이브러리 추천 (react-native-gifted-charts 사용방법) (0) | 2024.03.25 |
---|---|
[React-native CLI] Home화면 기능 구현하기 (0) | 2024.03.23 |
[React-native CLI] RN navigation.navigate 타입 오류 수정 (0) | 2024.03.22 |
[React-native CLI] RN에서 Realm 세팅 및 사용(구조 전면 교체) (0) | 2024.03.19 |
[React-native CLI] RN 기록화면 구현하기 3부 (realm에 create, update하기) (0) | 2024.03.19 |