๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐Ÿ“ฑ Cross Platform/React Native

[React Native] React Native + Node.js + MySQL To do list ๋งŒ๋“ค๊ธฐ 3 - React Native ํ™”๋ฉด ๊ตฌํ˜„ํ•˜๊ธฐ (Implement frontend)

by Fomagran ๐Ÿ’ป 2022. 7. 26.
728x90
๋ฐ˜์‘ํ˜•

์•ˆ๋…•ํ•˜์„ธ์š” Foma ์ž…๋‹ˆ๋‹ค!

 

์˜ค๋Š˜์€ React Native + CLI + TS๋กœ ํˆฌ ๋‘ ๋ฆฌ์ŠคํŠธ ํ™”๋ฉด์„ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ์ •๋ฆฌํ•ด ๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

 

๋ฐ”๋กœ ์‹œ์ž‘ํ• ๊ฒŒ์š”~


Tutorial

 


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

 

 

[Node.js] React Native + Node.js + MySQL To do list ๋งŒ๋“ค๊ธฐ 4 - Express๋กœ CRUD ๊ตฌํ˜„ํ•˜๊ธฐ (Building a CRUD API with Express

์•ˆ๋…•ํ•˜์„ธ์š” Foma ์ž…๋‹ˆ๋‹ค. ์˜ค๋Š˜์€ Node.js์—์„œ Express ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ํ™œ์šฉํ•˜์—ฌ MySQL ๋ฐ์ดํ„ฐ ๋ฒ ์ด์Šค์— ์ƒ์„ฑ, ์กฐํšŒ, ์ˆ˜์ •, ์‚ญ์ œ (CRUD) ๋ฅผ ๊ตฌํ˜„ํ•ด ๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ๋ฐ”๋กœ ์‹œ์ž‘ํ• ๊ฒŒ์š”~ Tutorial  React Native +..

fomaios.tistory.com


Source code

 

์•„๋ž˜ ๋ ˆํฌ์ง€ํ† ๋ฆฌ๋กœ ์ด๋™ํ•˜๋ฉด ์ „์ฒด ์†Œ์Šค๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

๊ถ๊ธˆํ•˜์‹  ์  ์žˆ์œผ์‹œ๋ฉด ์–ธ์ œ๋“  ๋Œ“๊ธ€ ๋‹ฌ์•„์ฃผ์„ธ์š”!

 

 

GitHub - fomagran/ToDoListFullStack: React native(CLI,TS) + Node.js + MySQL

React native(CLI,TS) + Node.js + MySQL. Contribute to fomagran/ToDoListFullStack development by creating an account on GitHub.

github.com

 

728x90
๋ฐ˜์‘ํ˜•

๋Œ“๊ธ€