[React Native] React Native + Node.js + MySQL To do list ๋ง๋ค๊ธฐ 3 - React Native ํ๋ฉด ๊ตฌํํ๊ธฐ (Implement frontend)
์๋ ํ์ธ์ Foma ์ ๋๋ค!
์ค๋์ React Native + CLI + TS๋ก ํฌ ๋ ๋ฆฌ์คํธ ํ๋ฉด์ ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ ๋ํด ์ ๋ฆฌํด ๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
๋ฐ๋ก ์์ํ ๊ฒ์~
Tutorial
- React Native + Node.js + MySQL To do list ๋ง๋ค๊ธฐ 1 - ์๋ฒ ์ด๊ธฐ ์ธํ ํ๊ธฐ (Initialize server setting)
- React Native + Node.js + MySQL To do list ๋ง๋ค๊ธฐ 2 - MySQL ํ ์ด๋ธ ๋ง๋ค๊ธฐ (Create a MySQL Table)
- React Native + Node.js + MySQL To do list ๋ง๋ค๊ธฐ 3 - React Native ํ๋ฉด ๊ตฌํํ๊ธฐ (Implement frontend)
- React Native + Node.js + MySQL To do list ๋ง๋ค๊ธฐ 4 - Express๋ก CRUD ๊ตฌํํ๊ธฐ (Building a CRUD API with Express)
- React Native + Node.js + MySQL To do list ๋ง๋ค๊ธฐ 5 - Axios ์ด์ฉํ์ฌ CRUD ๊ตฌํํ๊ธฐ (Implementing a api with Axios)
Init React Native Typescript Project (CLI)
React Native ํ๋ก์ ํธ๋ฅผ typescript ํ ํ๋ฆฟ์ผ๋ก ๋ง๋ค์ด ์ค๋๋ค.
(์ ๋ ์ต์ ๋ฒ์ ์ผ๋ก ํ๋ ์ค๋ฅ๊ฐ ๋์ ๋ฒ์ ์ ๋ฎ์ถฐ์ ํ ํ๋ฆฟ์ ๋ง๋ค์์ต๋๋ค.)
npx react-native init ToDoList --template react-native-template-typescript@6.6.2
Install modules
react-native-vector-icons
npm i react-native-vector-icons@8.1.0 --save
//link
npx react-native link react-native-vector-icons
Models
TodoModel.ts
todo ์์ดํ ์ ์ธํฐํ์ด์ค๋ฅผ ์ ์ํด ์ค๋๋ค.
interface TodoModel {
id: number;
author: string;
title: string;
content: string;
priority: number;
}
Components
Todo.tsx
TodoList์ ์์ดํ ํ๋ฉด์ ๊ตฌํํด ์ค๋๋ค.
ํ๋กํผํฐ๋ฅผ ์ธํฐํ์ด์ค๋ก ์ ์ํด ์ค๋๋ค.
interface ToDoProps {
index: number;
todo: TodoModel;
handleDelete: () => {};
handleEdit: () => {};
}
๊ทธ ๋ค์ ์ญ์ ๋ฒํผ๊ณผ ์์ ๋ฒํผ์ด ๋๋ ธ์ ๋ ์ฝ๋ฐฑ์ผ๋ก ํด๋น index๋ฅผ ๋๊ฒจ์ฃผ๋๋ก ํ๋ ํจ์๋ฅผ ๋ง๋ค์ด ์ค๋๋ค.
const ToDo: React.FC<ToDoProps> = props => {
const handleDelete = () => {
props.handleDelete(props.index);
};
const handleEdit = () => {
props.handleEdit(props.index);
};
๋ทฐ๋ฅผ ๊ตฌ์ฑํด ์ฃผ๊ณ , ๊ฐ ๋ฒํผ์ด ๋๋ ธ์ ๋ ์์์ ๊ตฌํํ ํจ์๊ฐ ์คํ๋๋๋ก ์์ฑํด ์ค๋๋ค.
return (
<View style={styles.container}>
<Text style={styles.title}>
{props.todo.title} - {props.todo.author}
</Text>
<Text style={styles.content}>{props.todo.content}</Text>
<View style={styles.footer}>
<Pressable onPress={handleEdit}>
<Icon style={styles.update} name="surgical-knife"></Icon>
</Pressable>
<Pressable onPress={handleDelete}>
<Icon style={styles.trash} name="trash"></Icon>
</Pressable>
</View>
</View>
);
};
export default ToDo;
(styles๋ ๊ธ์ด ๋๋ฌด ๊ธธ์ด์ ธ์ ์๋ตํ๊ฒ ์ต๋๋ค ใ ๊ธ ๋งจ ์๋ ๊นํ ์์ค ์ฝ๋๋ฅผ ์ฐธ๊ณ ํด ์ฃผ์ธ์!)
AddModal.tsx
์ถ๊ฐ ๋ฒํผ์ด๋ ์์ ๋ฒํผ์ด ๋๋ ธ์ ๋์ ๋ชจ๋ฌ์ ๊ตฌํํด ์ค๋๋ค.
AddModalProps ์ธํฐํ์ด์ค๋ฅผ ์ ์ํด ์ค๋๋ค.
interface AddModalProps {
isModalVisible: boolean;
todo: TodoModel;
handleClose: () => {};
handleSubmit: () => {};
}
todo์ ์ํ๋ฅผ useState ์ ์ฅํ๊ณ useEffect๋ฅผ ํตํด props์ todo๊ฐ ๋ฐ๋ ๋๋ง๋ค ํด๋น todo ์ํ๊ฐ ๋ฐ๋๋๋ก ๊ตฌํํฉ๋๋ค.
(์์ธํ ๊ฑด ์ฌ๊ธฐ ๋ฅผ ์ฐธ๊ณ ํด ์ฃผ์ธ์!)
๊ทธ ๋ค์ handleInputChange๋ฅผ ํตํด ์ธํ ๊ฐ์ด ๋ฐ๋ ๋๋ง๋ค ํด๋น๋๋ todo์ ํ๋กํผํฐ ๊ฐ์ด ๋ฐ๋๋๋ก ๊ตฌํํด ์ค๋๋ค.
(์์ธํ ๊ฑด ์ฌ๊ธฐ ๋ฅผ ์ฐธ๊ณ ํด ์ฃผ์ธ์!)
๋ฐ์ดํฐ๋ฅผ ์ด๊ธฐํ ์ํฌ cleanup ํจ์๋ฅผ ๊ตฌํํด ์ค๋๋ค.
const AddModal: React.FC<AddModalProps> = props => {
const [todo, setTodo] = useState<TodoModel>({
author: '',
title: '',
content: '',
priority: -1,
});
useEffect(() => {
setTodo(props.todo);
}, [props]);
const handleInputChange = (key: string, value: string | number) => {
setTodo(prevState => ({
...prevState,
[key]: value,
}));
};
const cleanup = () => {
setTodo({
author: '',
title: '',
content: '',
priority: -1,
});
};
๋ชจ๋ฌ ์์ ๊ฐ ์ธํ ์ฐฝ๊ณผ ๋ฒํผ์ ๊ตฌํํด ์ค๋๋ค.
return (
<Modal
animationType="slide"
transparent={true}
presentationStyle={'overFullScreen'}
visible={props.isModalVisible}
onRequestClose={() => {
props.handleClose();
}}>
<View style={styles.container}>
<View style={styles.form}>
<TextInput
value={todo.author}
onChangeText={text => {
handleInputChange('author', text);
}}
style={styles.input}
placeholder="author"></TextInput>
<TextInput
value={todo.title}
style={styles.input}
placeholder="title"
onChangeText={text => {
handleInputChange('title', text);
}}></TextInput>
<TextInput
value={todo.content}
style={styles.input}
placeholder="content"
onChangeText={text => {
handleInputChange('content', text);
}}></TextInput>
<TextInput
value={String(todo.priority)}
style={styles.input}
placeholder="priority"
onChangeText={text => {
handleInputChange('priority', Number(text));
}}></TextInput>
<View style={styles.closeContainer}>
<Pressable
onPress={() => {
if (!props.isModalVisible) {
cleanup();
}
props.handleSubmit(todo);
}}>
<Text style={styles.button}> Submit </Text>
</Pressable>
<Pressable
onPress={() => {
console.log(props.todo);
cleanup();
props.handleClose();
}}>
<Text style={styles.button}> Close </Text>
</Pressable>
</View>
</View>
</View>
</Modal>
);
};
export default AddModal;
Screens
TodoList.tsx
ํฌ ๋ ๋ฆฌ์คํธ ํ๋ฉด์ ๊ตฌํํด ์ค๋๋ค.
๋จผ์ ํ์ํ ์ํ ํ๋กํผํฐ๋ค์ ์ ์ํด ์ค๋๋ค.
/** Properties */
const [todos, setTodos] = useState(todoDatas);
const [isModalVisible, setModalVisible] = useState(false);
const [todo, setTodo] = useState<TodoModel>({
author: '',
title: '',
content: '',
priority: -1,
});
const [editIndex, setEditIndex] = useState(-1);
์์ ์ด๋ ์ญ์ ๋ฒํผ์ด ๋๋ ธ์ ๋ ๋จ๋ Alert ํจ์๋ฅผ ๊ตฌํํด ์ค๋๋ค.
const showEditAlert = (index: number) => {
Alert.alert(
'Edit',
'Do you want to edit?',
[
{
text: 'Cancel',
onPress: () => console.log('Cancel Pressed!'),
},
{
text: 'OK',
onPress: () => {
setEditIndex(index);
setTodo(todos[index]);
setModalVisible(true);
},
},
],
{cancelable: false},
);
};
const showDeleteAlert = (index: number) => {
Alert.alert(
'Delete',
'Do you want to delete?',
[
{
text: 'Cancel',
onPress: () => console.log('Cancel Pressed!'),
},
{
text: 'OK',
onPress: () => {
todos.splice(index, 1);
setTodos(todos);
},
},
],
{cancelable: false},
);
};
๊ฐ ๋ฒํผ์ด ๋๋ ธ์ ๋ ๋์ํ ํจ์๋ฅผ ๊ตฌํํด ์ค๋๋ค.
const handleEdit = (index: number) => {
showEditAlert(index);
};
const handleDelete = (index: number) => {
showDeleteAlert(index);
};
const handlePlus = () => {
setTodo({author: '', title: '', content: '', priority: -1});
setModalVisible(true);
};
const handleClose = () => {
setModalVisible(false);
};
const handleSubmit = (todo: TodoModel) => {
if (
todo.author == '' ||
todo.title == '' ||
todo.content == '' ||
todo.priority == -1
) {
Alert.alert('Please fill in all the required fields!');
} else {
let newTodos = todos;
if (editIndex !== -1) {
newTodos[editIndex] = todo;
setEditIndex(-1);
} else {
newTodos.push(todo);
}
setTodos(newTodos);
setModalVisible(false);
}
};
FlatList๋ฅผ ์ฌ์ฉํด ๊ฐ ์ปดํฌ๋ํธ๋ฅผ ๋์ฐ๋๋ก ํฉ๋๋ค.
return (
<View>
<View style={styles.header}>
<Text style={{marginLeft: 20, fontSize: 25, fontWeight: 'bold'}}>
To Do List
</Text>
<Pressable onPress={() => handlePlus()}>
<Icon name="plus-a" style={styles.plus} />
</Pressable>
</View>
<AddModal
isModalVisible={isModalVisible}
handleClose={handleClose}
handleSubmit={handleSubmit}
todo={todo}></AddModal>
<FlatList
style={styles.list}
contentContainerStyle={{paddingBottom: 50}}
data={todos}
keyExtractor={item => item.author}
renderItem={({item, index}) => {
return (
<ToDo
index={index}
todo={item}
handleDelete={handleDelete}
handleEdit={handleEdit}
/>
);
}}
/>
</View>
);
};
export default TodoList;
Next Tutorial
Source code
์๋ ๋ ํฌ์งํ ๋ฆฌ๋ก ์ด๋ํ๋ฉด ์ ์ฒด ์์ค๋ฅผ ํ์ธํ ์ ์์ต๋๋ค.
๊ถ๊ธํ์ ์ ์์ผ์๋ฉด ์ธ์ ๋ ๋๊ธ ๋ฌ์์ฃผ์ธ์!