@ List
* 7.7 useReducer Hook 함수 사용하기
* 7.8 AsyncStorage로 데이터 유지하기
* 7.9 정리
@ Note
1. useReducer 함수
- 상태를 관리할 때 사용하는 Hook 함수
- useState를 여러 번 사용하는 상황에서 사용하면 유용 할 수 있음
> 상황에 따라 useState로만 구현하는 게 편할 때도 있으므로, 반드시 사용할 필요는 없음
> 아래 처럼 각각 다른 상태를 동시에 업데이트하는 상황에는 useReducer로 구현하는 것을 고민해보면 좋음
const onPressDate = () => {
setMode('date');
setVisible(true);
};
- 기본 개념
> state : 상태
action : 변화를 정의하는 객체
reducer : state와 action을 파라미터로 받아와서 그다음 상태를 반환하는 함수
dispatch : action을 발생시키는 함수
- useReducer는 상태를 업데이트하는 로직을 컴포넌트 바깥에 구현할 수 있음
- duspatch라는 함수 하나로 다양하게 업데이트할 수 있기 때문에 Context와 함께 사용하면 유용함
- reference : https://reactjs.org/docs/hooks-reference.html#usereducer
@ Result
@ Git
- https://github.com/eunbok-bocoder/DayLog/commits/main
# Source tree
# components > WriteHeader.js
import {useNavigation} from '@react-navigation/native';
import {format} from 'date-fns';
import {ko} from 'date-fns/locale';
import React, {useState, useReducer} from 'react';
import {Pressable, StyleSheet, Text, View} from 'react-native';
import TransparentCircleButton from './TransparentCircleButton';
import DateTimePickerModal from 'react-native-modal-datetime-picker';
const initialState = {mode: 'date', visible: false};
function reducer(state, action) {
switch (action.type) {
case 'open':
return {
mode: action.mode,
visible: true,
};
case 'close':
return {
...state,
visible: false,
};
default:
throw new Error('Unhandled action type');
}
}
function WriteHeader({onSave, onAskRemove, isEditing, date, onChangeDate}) {
const navigation = useNavigation();
const onGoBack = () => {
navigation.pop();
};
const [state, dispatch] = useReducer(reducer, initialState);
const open = mode => dispatch({type: 'open', mode});
const close = () => dispatch({type: 'close'});
const onConfirm = selectedDate => {
close();
onChangeDate(selectedDate);
};
return (
<View style={styles.block}>
<View style={styles.iconButtonWrapper}>
<TransparentCircleButton
onPress={onGoBack}
name="arrow-back"
color="#424242"
/>
</View>
<View style={styles.buttons}>
{isEditing && (
<TransparentCircleButton
name="delete-forever"
color="#ef5350"
hasMarginRight
onPress={onAskRemove}
/>
)}
<TransparentCircleButton
name="check"
color="#009688"
onPress={onSave}
/>
</View>
<View style={styles.center}>
<Pressable onPress={() => open('date')}>
<Text>{format(new Date(date), 'PPP', {locale: ko})}</Text>
</Pressable>
<View style={styles.separator} />
<Pressable onPress={() => open('time')}>
<Text>{format(new Date(date), 'p', {locale: ko})}</Text>
</Pressable>
</View>
<DateTimePickerModal
isVisible={state.visible}
mode={state.mode}
onConfirm={onConfirm}
onCancel={close}
date={date}
/>
</View>
);
}
const styles = StyleSheet.create({
block: {
height: 48,
paddingHorizontal: 8,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
},
iconButtonWrapper: {
width: 32,
height: 32,
borderRadius: 16,
overflow: 'hidden',
},
iconButton: {
alignItems: 'center',
justifyContent: 'center',
width: 32,
height: 32,
borderRadius: 16,
},
buttons: {
flexDirection: 'row',
alignItems: 'center',
},
marginRight: {
marginRight: 8,
},
center: {
position: 'absolute',
left: 0,
right: 0,
top: 0,
bottom: 0,
alignItems: 'center',
justifyContent: 'center',
zIndex: -1,
flexDirection: 'row',
},
separator: {
width: 8,
},
});
export default WriteHeader;
# contexts > LogContext.js
import React, {useEffect, useRef} from 'react';
import {createContext, useState} from 'react';
import {v4 as uuidv4} from 'uuid';
import logsStorage from '../storage/logsStorage';
// const LogContext = createContext('안녕하세요.');
const LogContext = createContext();
export function LogContextProvider({children}) {
const initialLogsRef = useRef(null);
const [logs, setLogs] = useState([]);
// // 테스트 데이터 10개 생성
// const [logs, setLogs] = useState(
// Array.from({length: 10})
// .map((_, index) => ({
// id: uuidv4(),
// title: `Log ${index}`,
// body: `Log ${index}`,
// date: new Date().toISOString(),
// }))
// .reverse(),
// );
const onCreate = ({title, body, date}) => {
const log = {
id: uuidv4(),
title,
body,
date,
};
setLogs([log, ...logs]);
};
const onModify = modified => {
// logs 배열을 순회해 id가 일치하면 log를 교체하고 그렇지 않으면 유지
const nextLogs = logs.map(log => (log.id === modified.id ? modified : log));
setLogs(nextLogs);
};
const onRemove = id => {
const nextLogs = logs.filter(log => log.id !== id);
setLogs(nextLogs);
};
useEffect(() => {
// useEffect 내에서 async 함수를 만들고 바로 호출
// IIFE 패턴
(async () => {
const savedLogs = await logsStorage.get();
if (savedLogs) {
initialLogsRef.current = savedLogs;
setLogs(savedLogs);
}
})();
}, []);
useEffect(() => {
if (logs === initialLogsRef.current) {
return;
}
logsStorage.set(logs);
}, [logs]);
return (
<LogContext.Provider value={{logs, onCreate, onModify, onRemove}}>
{children}
</LogContext.Provider>
);
}
export default LogContext;
# storage > logsStorage.js
import AsyncStorage from '@react-native-community/async-storage';
const key = 'logs';
const logsStorage = {
async get() {
try {
const raw = await AsyncStorage.getItem(key);
const parsed = JSON.parse(raw);
return parsed;
} catch (e) {
throw new Error('Failed to load logs');
}
},
async set(data) {
try {
await AsyncStorage.setItem(key, JSON.stringify(data));
} catch (e) {
throw new Error('Fail to save logs');
}
},
};
export default logsStorage;
'React Native > React Native_study' 카테고리의 다른 글
[리액트 네이티브를 다루는 기술 #16] 8장 Firebase로 사진 공유 앱 만들기1 (p .419 ~ 444) (0) | 2022.01.26 |
---|---|
[리액트 네이티브를 다루는 기술 #15] 8장 Firebase로 사진 공유 앱 만들기1 (p .407 ~ 418) (0) | 2022.01.20 |
[리액트 네이티브를 다루는 기술 #13] 7장 다이어리 앱 만들기2 (p .380 ~ 398) (0) | 2022.01.14 |
[리액트 네이티브를 다루는 기술 #12] 7장 다이어리 앱 만들기2 (p .357 ~ 379) (2) | 2022.01.11 |
[리액트 네이티브를 다루는 기술 #11] 6장 다이어리 앱 만들기1 (p.330 ~ 356) (0) | 2022.01.07 |