
✔️ TouchableOpacity, ...styles
<TouchableOpacity onPress={work}>
<Text style={{...styles.btnText, color: working ? "white" : theme.toDoBg}}>Work</Text>
</TouchableOpacity>
<TouchableOpacity onPress={travel}>
<Text style={{...styles.btnText, color: !working ? "white" : theme.toDoBg}}>Travel</Text>
</TouchableOpacity>
header 에서 onPress 를 활용해서 work 와 travel 의 상태를 true 로 저장시켰다.
이 상태값을 활용해서 이후에 placeholder, list 들을 상태값에 따라 다르게 보여지게 한다.
styles 앞에 ... 을 붙이면 기존 객체를 그대로 가져와서 새로 추가하는 내용만 수정한다는 뜻이다.
✔️ TextInput
<TextInput
onSubmitEditing={addTodo}
returnKeyType={"done"}
value={text}
onChangeText={onChangeText}
placeholder={working ? "Add a To Do" : "Where do you wannt to go?"}
style={styles.textInput}
/>
TextInput 의 다양한 prop 을 이용해서 Customizing 해주었다.
완료 버튼을 done 으로 바꿔주었다.
onSubmitEditing 에 addTodo 함수를 연결해주었다.
✔️ Todo 저장
const onChangeText = (payload) => setText(payload)
onChangeText 는 text 를 받아와서 state 에 text 를 담았다.
const addTodo = async () => {
if (text === "") {
return;
}
const newToDos = {
...toDos, [Date.now()]: {text, working: working},
};
setToDos(newToDos)
await saveToDos(newToDos)
setText("");
console.log(toDos)
}
- toDos 가 비어있으면 그대로 끝내고,
- 새로운 toDo 가 있다면 기존에 toDos 에서 Date.now() 를 key 로 가지고 있는 새로운 객체와 결합해서 newToDos 를 만들어낸다.
- text 는 state 에서 가져오고 working 이 true 인지 false 인지 역시 기존에 선택한 tab 에 저장되어있는 state 로 부터 가져온다.
- 이후 setToDos 로 state 에 저장하고 saveToDos 함수를 호출한다.
- 그리고 setText("") 로 다시 입력창을 빈 칸으로 만들어둔다.
✔️ Todo삭제
<TouchableOpacity onPress={() => deleteToDo(key)}>
<Fontisto name="trash" size={18} color={theme.toDoBg} />
</TouchableOpacity>
휴지통 아이콘을 배치시키면서 클릭할 시에 onPress 에 deleteToDo 함수가 key 값과 호출되도록 설정했다.
const deleteToDo = async (key) => {
Alert.alert("Delete To Do?", "Are you sure?", [
{
text: "Ok",
onPress: () => {
const newToDos = {...toDos}
delete newToDos[key]
setToDos(newToDos);
saveToDos(newToDos);
}
},
{
text: "Cancel", style: "destructive"
}
]
)
}
Alert Api 를 활용해서 사용자에게 alert 를 보내주는데, 우리가 마음대로 메세지와 버튼 문구, callback 등을 설정할 수 있었다.
style 은 ios 에서만 가능했다.
✔️ JSON.stringify / JSON.parse
const saveToDos = async (toSave) => {
await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(toSave))
}
const loadToDos = async () => {
setLoading(true);
const s = await AsyncStorage.getItem(STORAGE_KEY);
if (s) {
setToDos(JSON.parse(s))
}
setLoading(false)
}
JSON.stringify 는 자바스크립트 객체를 string 으로 변환시켜주기 때문에 saveToDos 함수에서는 위와 같이 사용되었다면,
반대로 loadToDos 함수에서는 기존에 변환된 string 을 다시 자바스크립트 객체로 변환시켜주기 위해서 JSON.parse(s) 를 사용했다.
useEffect(() => {
loadToDos();
}, []);
useEffect 에 loadToDos 를 넣어서 화면이 로딩되면 자동으로 데이터들이 보이도록 설정했다.

✔️ 전체 코드
import {StatusBar} from 'expo-status-bar';
import {Keyboard, TouchableWithoutFeedback} from "react-native";
import {ActivityIndicator, Alert, ScrollView, StyleSheet, Text, TextInput, TouchableOpacity, View} from 'react-native';
import React, {useEffect, useState} from "react";
import {theme} from "./colors";
import AsyncStorage from "@react-native-async-storage/async-storage";
import Fontisto from '@expo/vector-icons/Fontisto';
const STORAGE_KEY_TODO = "@toDos"
const STORAGE_KEY_MENU = "@menu"
export default function App() {
const [working, setWorking] = useState(true);
const [isCompleted, setIsCompleted] = useState(false);
const [text, setText] = useState("");
const [toDos, setToDos] = useState({});
const [loading, setLoading] = useState(true);
const [editingKey, setEditingKey] = useState(null);
const [editText, setEditText] = useState("");
const travel = async () => {
setWorking(false);
await saveMenu(false)
};
const work = async () => {
setWorking(true);
await saveMenu(true);
}
const saveMenu = async (isWorking) => {
await AsyncStorage.setItem(STORAGE_KEY_MENU, JSON.stringify(isWorking))
}
const loadMenuState = async () => {
const savedState = await AsyncStorage.getItem(STORAGE_KEY_MENU);
if (savedState !== null) {
setWorking(JSON.parse(savedState));
}
}
const onChangeText = (payload) => setText(payload)
const saveToDos = async (toSave) => {
await AsyncStorage.setItem(STORAGE_KEY_TODO, JSON.stringify(toSave))
}
const loadToDos = async () => {
setLoading(true);
const s = await AsyncStorage.getItem(STORAGE_KEY_TODO);
if (s) {
setToDos(JSON.parse(s))
}
setLoading(false)
}
useEffect(() => {
loadToDos();
loadMenuState();
}, []);
const addTodo = async () => {
if (text === "") {
return;
}
const newToDos = {
...toDos, [Date.now()]: {text, working: working, isCompleted: isCompleted},
};
setToDos(newToDos)
await saveToDos(newToDos)
setText("");
console.log(toDos)
}
const deleteToDo = async (key) => {
Alert.alert("Delete To Do?", "Are you sure?", [
{
text: "Ok",
onPress: () => {
const newToDos = {...toDos}
delete newToDos[key]
setToDos(newToDos);
saveToDos(newToDos);
}
},
{
text: "Cancel", style: "destructive"
}
]
)
}
const editToDo = (key, currentText) => {
setEditingKey(key);
setEditText(currentText);
console.log(key, currentText)
}
const saveEdit = async () => {
if (editText.trim() === "") return;
const updatedToDos = {
...toDos,
[editingKey]: {...toDos[editingKey], text: editText}
};
setToDos(updatedToDos);
await saveToDos(updatedToDos);
setEditingKey(null);
setEditText("");
};
const updateToDos = async (key) => {
const updatedToDos = {
...toDos,
[key]: {...toDos[key], isCompleted: !toDos[key].isCompleted}
}
setToDos(updatedToDos);
await saveToDos(updatedToDos);
}
return (
<TouchableWithoutFeedback onPress={Keyboard.dismiss} accessible={false}>
<View style={styles.container}>
<StatusBar style="auto"/>
<View style={styles.header}>
<TouchableOpacity onPress={work}>
<Text style={{...styles.btnText, color: working ? "white" : theme.toDoBg}}>Work</Text>
</TouchableOpacity>
<TouchableOpacity onPress={travel}>
<Text style={{...styles.btnText, color: !working ? "white" : theme.toDoBg}}>Travel</Text>
</TouchableOpacity>
</View>
<View>
<TextInput
onSubmitEditing={addTodo}
returnKeyType={"done"}
value={text}
onChangeText={onChangeText}
placeholder={working ? "Add a To Do" : "Where do you wannt to go?"}
style={styles.textInput}
/>
{
loading ? (
<ActivityIndicator size="large" color="white" style={styles.loading}/>
) : (
<ScrollView>
{
Object.keys(toDos).map(key =>
toDos[key].working === working ? (
<View style={styles.toDo} key={key}>
{editingKey === key ? (
<TextInput
value={editText}
onChangeText={setEditText}
onSubmitEditing={saveEdit}
style={styles.editInput}
autoFocus
/>
) : (
<Text
style={[
styles.toDoText,
toDos[key].isCompleted && {
textDecorationLine: "line-through",
color: "gray"
}
]}
>
{toDos[key].text}
</Text>
)}
<View style={styles.btnContainer}>
<TouchableOpacity onPress={() => updateToDos(key)}>
<Fontisto name="check" size={18}
color={toDos[key].isCompleted ? "gray" : theme.toDoBg}/>
</TouchableOpacity>
<TouchableOpacity onPress={() => editToDo(key, toDos[key].text)}>
<Fontisto name="commenting" size={18} color={theme.toDoBg}/>
</TouchableOpacity>
<TouchableOpacity onPress={() => deleteToDo(key)}>
<Fontisto name="trash" size={18} color={theme.toDoBg}/>
</TouchableOpacity>
</View>
</View>
) : null
)}
</ScrollView>
)
}
</View>
</View>
</TouchableWithoutFeedback>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: theme.bg,
paddingHorizontal: 20,
},
header: {
justifyContent: "space-between",
flexDirection: "row",
marginTop: 100,
},
btnText: {
fontSize: 38,
fontWeight: "600",
},
textInput: {
backgroundColor: "white",
paddingVertical: 15,
paddingHorizontal: 20,
borderRadius: 20,
marginVertical: 20,
fontSize: 18,
},
toDo: {
backgroundColor: theme.bg,
marginBottom: 10,
paddingVertical: 10,
paddingHorizontal: 40,
borderRadius: 15,
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between"
},
toDoText: {
color: "white",
fontSize: 16,
fontWeight: "500",
},
loading: {
marginTop: 20,
},
btnContainer: {
flexDirection: "row",
gap: 10,
},
editInput: {
backgroundColor: "white",
paddingVertical: 5,
paddingHorizontal: 10,
borderRadius: 10,
fontSize: 16,
flex: 0.8,
},
});
'컴퓨터 프로그래밍 > React Native' 카테고리의 다른 글
[React Native] 날씨 어플리케이션 (0) | 2025.03.29 |
---|---|
[React Native] React Native 공식문서 및 Expo SDK, Layout, Component (0) | 2025.03.27 |
[React Native] 개념 및 작동원리, 환경세팅 (0) | 2025.03.26 |