[React Native] 로그인 화면 구현 (프론트엔드) - 2. TextIput 이벤트 핸들러

2022. 5. 22. 19:37React Native/Intermediate

 

 


 


 

 


1. TextIput 이벤트 핸들러 및 함수

textInput 에 커서를 두고 클릭을 하고 키보드 입력을 해도 아무런 반응이 없다. 텍스트가 입력이 될 때마다 이벤트를 발생시키는 onChange 라는 텍스트 프로퍼티가 필요하다.

단순히 입력된 값을 업데이트 시키는 함수만이 아니라 이번에는 onChangeText라는 이벤트 핸들러에 화살표 함수를 사용할 것이다. 여기에 value라는 매개변수를 전달해 값을 바로 업데이트시킬 것이다. 

 

updateInput 함수에는 2가지 인자를 넣는데 하나는 string, 하나는 value를 넣자. string 값을 써주는 이유는 2가지가 있다.

1. 이메일, 비밀번호 textInput에서 구분하기 위한 인자를 만들기 위해서

2. 업데이트되는 value 값은 state에서 선언한 Form 객체 내부의 value 값을 업데이트 하기 위해서 쓰일 것인데, Form 객체 안에 email, password, confirmPassword 라는 이름의 객체가 존재하기 때문에 그것과 동일한 이름을 써주기 위함이다.

 

강의는 클래스형으로 진행되었지만 함수형으로 진행하면서 코드 상에 state 관련 변화가 있다.

// /app/components/auth/authForm.js
/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 *
 * @format
 * @flow strict-local
 */

import React, {useState} from 'react';
import {StyleSheet, Text, View, TextInput} from 'react-native';
import Input from '../../utils/forms/input';
const AuthForm = () => {
  const [type, setType] = useState('Login');
  const [action, setAction] = useState('Login');
  const [actionMode, setActionMode] = useState('새로 등록할게요~');
  const [hasErrors, setHasErrors] = useState(false);
  const [form, setForm] = useState({
    email: {
      value: '',
      type: 'textInputRevised',
      rules: {},
      valid: false,
    },
    password: {
      value: '',
      type: 'textInput',
      rules: {},
      valid: false,
    },
    confirmPassword: {
      value: '',
      type: 'textInput',
      rules: {},
      valid: false,
    },
  });

  //   state = {
  //     type: 'Login',
  //     action: 'Login',
  //     actionMode: '새로 등록할게요~',
  //     hasErrors: false,
  //     form: {
  //       email: {
  //         value: '',
  //         type: 'textInputRevised',
  //         rules: {},
  //         valid: false,
  //       },
  //       password: {
  //         value: '',
  //         type: 'textInput',
  //         rules: {},
  //         valid: false,
  //       },
  //       confirmPassword: {
  //         value: '',
  //         type: 'textInput',
  //         rules: {},
  //         valid: false,
  //       },
  //     },
  //   };
  updateInput = (name, value) => {
    setHasErrors(false);
    // let formCopy = form;
    // formCopy[name].value = value;
    setForm(form => {
      return {...form, value: value};
    });
    // setForm({
    //   form: formCopy,
    // });
    console.warn(form);
  };

  return (
    <View>
      <Input
        value={form.email.value}
        type={form.email.type}
        autoCapitalize={'none'}
        keyboardType={'email-address'}
        placeholder="이메일 주소"
        placeholderTextColor={'#ddd'}
        onChangeText={value => updateInput('email', value)}
      />
      <Input
        value={form.password.value}
        type={form.password.type}
        secureTextEntry={true}
        placeholder="비밀번호"
        placeholderTextColor={'#ddd'}
      />
    </View>
  );
};

const styles = StyleSheet.create({});

export default AuthForm;

 


2. 로그인 화면 만들기

아래 파일들은 모두 auth 디렉토리 안의 파일들이다.

authLogo.js 에서는 로고 이미지를 import 해오자. 이미지 태그를 이용해 해당 이미지를 보여주자.

// /app/components/auth/authLogo.js

import React from 'react';
import {View, Image} from 'react-native';

import LogoImage from '../../assets/imgs/winthiary_login_logo.png';

const LogoComponent = () => (
  <View style={{alignItems: 'center'}}>
    <Image
      source={LogoImage}
      resizeMode={'contain'}
      style={{
        width: 300,
        height: 88,
      }}
    />
  </View>
);

export default LogoComponent;

authForm.js 에서는 우선 간단히 틀만 잡아두자.

// /app/components/auth/authForm.js
/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 *
 * @format
 * @flow strict-local
 */

import React, {Component} from 'react';
import {StyleSheet, Text, View} from 'react-native';

const AuthForm = () => {
  return (
    <View>
      <Text>Hello</Text>
    </View>
  );
};

const styles = StyleSheet.create({});

export default AuthForm;

index.js 에서는 authLogo, authForm 파일에서 컴포넌트를 import 해오자.  로딩하는 중인지 아닌지 판단하기 위해 ActivityIndicator를 import 하고 뷰가 넘칠 수 있으므로 ScrollView를 import 하자.

state를 정의하고 loading을 정의하자. 그리고 이 loading 값이 true면 로딩 중인 화면을 보여주고 아니면 스크롤뷰 내에서 로고와 양식을 보여준다.

 

// /app/components/auth/index.js

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 *
 * @format
 * @flow strict-local
 */

import React, {Component} from 'react';
import {
  StyleSheet,
  Text,
  View,
  ActivityIndicator,
  ScrollView,
} from 'react-native';
import AuthLogo from './authLogo';
import AuthForm from './authForm';

const AuthComponent = () => {
  state = {
    loading: false,
  };
  if (this.state.loading) {
    return (
      <View style={styles.loading}>
        <ActivityIndicator />
      </View>
    );
  } else {
    return (
      <ScrollView style={styles.container}>
        <View>
          <AuthLogo />
          <AuthForm />
        </View>
      </ScrollView>
    );
  }
};

const styles = StyleSheet.create({
  loading: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
  container: {
    flex: 1,
    backgroundColor: '#7487C5',
    padding: 130,
  },
});

export default AuthComponent;

3. 재사용 컴포넌트 작성

재사용 가능한 컴포넌트를 활용하여 id,pw를 입력하는 textinput을 구현해보자.

우선 재사용 가능한 컴포넌트 제작을 위해 파일을 새로 만들자. /app 폴더 내에 utils라는 폴더를 만들고 그 안에 forms라는 폴더를 만들자. 그 안에 input.js 라는 파일을 만들자.

 

input.js 파일 안에 재사용 가능한 컴포넌트를 만들고 authForm.js에서 이 컴포넌트를 가져와 사용하자.

 

일단 authForm.js에서 날코드를 먼저 작성해보자.

 

// /app/components/auth/authLogo.js
import React from 'react';
import {View, Image} from 'react-native';

import LogoImage from '../../assets/imgs/winthiary_login_logo.png';

const LogoComponent = () => (
  <View style={{alignItems: 'center', marginBottom: 30}}>
    <Image
      source={LogoImage}
      resizeMode={'contain'}
      style={{
        width: 300,
        height: 88,
      }}
    />
  </View>
);

export default LogoComponent;
// /app/components/auto/authForm.js

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 *
 * @format
 * @flow strict-local
 */

import React, {Component} from 'react';
import {StyleSheet, Text, View, TextInput} from 'react-native';

const AuthForm = () => {
  state = {
    myTextInput: '',
  };
  return (
    <View>
      <TextInput
        value={this.state.myTextInput}
        autoCapitalize={'none'}
        keyboardType={'email-address'}
        style={styles.input}
        placeholder="이메일 주소"
        placeholderTextColor={'#ddd'}
      />
      <TextInput
        value={this.state.myTextInput}
        autoCapitalize={'none'}
        keyboardType={'email-address'}
        style={styles.input}
        placeholder="비밀번호"
        placeholderTextColor={'#ddd'}
      />
    </View>
  );
};

const styles = StyleSheet.create({
  input: {
    width: '100%',
    borderBottomWidth: 1,
    borderBottomColor: '#fff',
    fontSize: 17,
    padding: 5,
    marginTop: 30,
  },
});

export default AuthForm;

 

react native 공식문서 - keyboardType 

https://reactnative.dev/docs/textinput#keyboardtype

 

TextInput · React Native

A foundational component for inputting text into the app via a keyboard. Props provide configurability for several features, such as auto-correction, auto-capitalization, placeholder text, and different keyboard types, such as a numeric keypad.

reactnative.dev

 

react native 공식문서 - placeholder

https://reactnative.dev/docs/textinput#placeholder 

 

TextInput · React Native

A foundational component for inputting text into the app via a keyboard. Props provide configurability for several features, such as auto-correction, auto-capitalization, placeholder text, and different keyboard types, such as a numeric keypad.

reactnative.dev

 

 

위의 날코드를 재사용해보자!

스타일만 공통이고 placeholder 같은 경우는 input.js에서 인지하고 있어야 하기 때문에 Input 함수에서 props를 받아오도록 할 것이다. 모든 props를 받아오기 위해 spread operator를 사용할 것이다. 

props를 인자로 받아오면 authForm.js에서 정의한 변수와 값이 그대로 전달되며 spread operator를 쓰면 전달된 값을 그대로 쓴다는 의미이다. 

스타일을 하나의 세트로 지정하고 컴포넌트로 제작했기 때문에 textinput을 하나 더 추가하고 싶다면 Input 을 하나 더 추가하면 된다.

 

// /app/utils/forms/input.js

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 *
 * @format
 * @flow strict-local
 */

import React from 'react';
import {StyleSheet, TextInput} from 'react-native';

const Input = props => {
  return <TextInput {...props} style={styles.input} />;
};

const styles = StyleSheet.create({
  input: {
    width: '100%',
    borderBottomWidth: 1,
    borderBottomColor: '#fff',
    fontSize: 17,
    padding: 5,
    marginTop: 30,
  },
});

export default Input;
// /app/components/auth/authForm.js
/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 *
 * @format
 * @flow strict-local
 */

import React, {Component} from 'react';
import {StyleSheet, Text, View, TextInput} from 'react-native';
import Input from '../../utils/forms/input';
const AuthForm = () => {
  state = {
    myTextInput: '',
  };
  return (
    <View>
      <Input
        value={this.state.myTextInput}
        autoCapitalize={'none'}
        keyboardType={'email-address'}
        placeholder="이메일 주소"
        placeholderTextColor={'#ddd'}
      />
      <Input
        value={this.state.myTextInput}
        autoCapitalize={'none'}
        keyboardType={'email-address'}
        placeholder="비밀번호"
        placeholderTextColor={'#ddd'}
      />
    </View>
  );
};

const styles = StyleSheet.create({});

export default AuthForm;

 

이 경우에는 같은 파일 안에서 위아래에 똑같은 컴포넌트를 복사 붙여넣기 해도 되기 때문에 큰 메리트를 느끼지 못 할 수도 있다. 하지만 앱을 기획하고 mockup까지 만들었다고 하자. 여러 화면으로 구성되는데 각 화면에 똑같은 textInput이 여러 번 사용된다고 하자. 그럼 스크린을 구성하는 파일마다 그 textInput에 대한 스타일을 지정해줘야 할텐데 컴포넌트로 만들면 이 수고를 덜 수 있다는 것이다!

 


4. 효율적인 코드 작성을 위한 state 활용

로그인 화면 구현을 하고 있는데, 로그인만 있는 게 아니라 회원가입도 있다. 그런데 둘이 완전히 같은 것도 다른 것도 아니고 미묘하게 유사한 부분이 많다.

유사하다는 것은 화면 구성 시 재사용할 수 있는 부분이 많다는 것이고 미묘하다는 것은 분명 차이를 줘야한다는 것이다. 

 

각 화면을 날코드로 작성해도 되지만 효율적인 것을 위해 컴포넌트를 재활용하는 것이고 거기에 state를 활용하는 것이다.

 

// /app/components/auth/authForm.js

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 *
 * @format
 * @flow strict-local
 */

import React, {Component} from 'react';
import {StyleSheet, Text, View, TextInput} from 'react-native';
import Input from '../../utils/forms/input';
const AuthForm = () => {
  state = {
    type: 'Login',
    action: 'Login',
    actionMode: '새로 등록할게요~',
    hasErrors: false,
    form: {
      email: {
        value: '',
        type: 'textInputRevised',
        rules: {},
        valid: false,
      },
      password: {
        value: '',
        type: 'textInput',
        rules: {},
        valid: false,
      },
      confirmPassword: {
        value: '',
        type: 'textInput',
        rules: {},
        valid: false,
      },
    },
  };
  return (
    <View>
      <Input
        value={this.state.form.email.value}
        type={this.state.form.email.type}
        autoCapitalize={'none'}
        keyboardType={'email-address'}
        placeholder="이메일 주소"
        placeholderTextColor={'#ddd'}
      />
      <Input
        value={this.state.form.password.value}
        type={this.state.form.password.type}
        secureTextEntry={true}
        placeholder="비밀번호"
        placeholderTextColor={'#ddd'}
      />
    </View>
  );
};

const styles = StyleSheet.create({});

export default AuthForm;
// /app/utils/forms/input.js
/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 *
 * @format
 * @flow strict-local
 */

import React from 'react';
import {StyleSheet, TextInput} from 'react-native';

const Input = props => {
  let template = null;
  switch (props.type) {
    case 'textInput':
      template = <TextInput {...props} style={styles.input} />;
      break;
    case 'textInputRevised':
      template = <TextInput {...props} style={styles.inputRevised} />;
      break;
    default:
      return template;
  }
  return template;
};

const styles = StyleSheet.create({
  input: {
    width: '100%',
    borderBottomWidth: 1,
    borderBottomColor: '#fff',
    fontSize: 17,
    padding: 5,
    marginTop: 30,
  },
  inputRevised: {
    width: '100%',
    borderBottomWidth: 3,
    borderBottomColor: 'red',
    fontSize: 17,
    padding: 5,
    marginTop: 30,
  },
});

export default Input;

참고로 실제 textInput에는 type이 없지만 재사용하기 위한 자유도를 높이기 위해 type을 state로 지정한 것이다.

 

함수형에 맞춰 수정한 코드

// /app/components/auth/authForm.js
/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 *
 * @format
 * @flow strict-local
 */

import React, {useState} from 'react';
import {StyleSheet, Text, View, TextInput} from 'react-native';
import Input from '../../utils/forms/input';
const AuthForm = () => {
  const [type, setType] = useState('Login');
  const [action, setAction] = useState('Login');
  const [actionMode, setActionMode] = useState('새로 등록할게요~');
  const [hasErrors, setHasErrors] = useState(false);
  const [form, setForm] = useState({
    email: {
      value: '',
      type: 'textInputRevised',
      rules: {},
      valid: false,
    },
    password: {
      value: '',
      type: 'textInput',
      rules: {},
      valid: false,
    },
    confirmPassword: {
      value: '',
      type: 'textInput',
      rules: {},
      valid: false,
    },
  });

  //   state = {
  //     type: 'Login',
  //     action: 'Login',
  //     actionMode: '새로 등록할게요~',
  //     hasErrors: false,
  //     form: {
  //       email: {
  //         value: '',
  //         type: 'textInputRevised',
  //         rules: {},
  //         valid: false,
  //       },
  //       password: {
  //         value: '',
  //         type: 'textInput',
  //         rules: {},
  //         valid: false,
  //       },
  //       confirmPassword: {
  //         value: '',
  //         type: 'textInput',
  //         rules: {},
  //         valid: false,
  //       },
  //     },
  //   };
  updateInput = (name, value) => {
    setHasErrors(false);
    let formCopy = form;
    formCopy[name].value = value;
    setForm(form => {
      return {...formCopy};
    });
    // setForm({
    //   form: formCopy,
    // });
    console.warn(form);
  };

  return (
    <View>
      <Input
        value={form.email.value}
        type={form.email.type}
        autoCapitalize={'none'}
        keyboardType={'email-address'}
        placeholder="이메일 주소"
        placeholderTextColor={'#ddd'}
        onChangeText={value => updateInput('email', value)}
      />
      <Input
        value={form.password.value}
        type={form.password.type}
        secureTextEntry={true}
        placeholder="비밀번호"
        placeholderTextColor={'#ddd'}
        onChangeText={value => updateInput('password', value)}
      />
    </View>
  );
};

const styles = StyleSheet.create({});

export default AuthForm;

 

로그인에서는 이메일, 비밀번호만 받으면 되지만 회원가입에서는 비밀번호 확인기능도 필요하다. 이 textInput 함수와 다를 때 에러메시지를 내보내는 함수를 만들어보자.

 

비밀번호 확인 기능과 에러 메시지를 내보내는 함수를 작성한 코드

// /app/components/auth/authForm.js
/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 *
 * @format
 * @flow strict-local
 */

import React, {useState} from 'react';
import {StyleSheet, Text, View, TextInput} from 'react-native';
import Input from '../../utils/forms/input';
const AuthForm = () => {
  const [type, setType] = useState('Login');
  const [action, setAction] = useState('Login');
  const [actionMode, setActionMode] = useState('새로 등록할게요~');
  const [hasErrors, setHasErrors] = useState(true);
  const [form, setForm] = useState({
    email: {
      value: '',
      type: 'textInput',
      rules: {},
      valid: false,
    },
    password: {
      value: '',
      type: 'textInput',
      rules: {},
      valid: false,
    },
    confirmPassword: {
      value: '',
      type: 'textInput',
      rules: {},
      valid: false,
    },
  });

  //   state = {
  //     type: 'Login',
  //     action: 'Login',
  //     actionMode: '새로 등록할게요~',
  //     hasErrors: false,
  //     form: {
  //       email: {
  //         value: '',
  //         type: 'textInputRevised',
  //         rules: {},
  //         valid: false,
  //       },
  //       password: {
  //         value: '',
  //         type: 'textInput',
  //         rules: {},
  //         valid: false,
  //       },
  //       confirmPassword: {
  //         value: '',
  //         type: 'textInput',
  //         rules: {},
  //         valid: false,
  //       },
  //     },
  //   };
  updateInput = (name, value) => {
    setHasErrors(false);
    let formCopy = form;
    formCopy[name].value = value;
    setForm(form => {
      return {...formCopy};
    });
    // setForm({
    //   form: formCopy,
    // });
    console.warn(form);
  };

  confirmPassword = () => {
    return type != 'Login' ? (
      <Input
        value={form.confirmPassword.value}
        type={form.confirmPassword.type}
        secureTextEntry={true}
        placeholder="비밀번호 재입력"
        placeholderTextColor={'#ddd'}
        onChangeText={value => updateInput('confirmPassword', value)}
      />
    ) : null;
  };
  formHasErrors = () => {
    return hasErrors ? (
      <View style={styles.errorContainer}>
        <Text style={styles.errorLabel}>
          앗! 로그인 정보를 다시 확인해주세요~
        </Text>
      </View>
    ) : null;
  };
  return (
    <View>
      <Input
        value={form.email.value}
        type={form.email.type}
        autoCapitalize={'none'}
        keyboardType={'email-address'}
        placeholder="이메일 주소"
        placeholderTextColor={'#ddd'}
        onChangeText={value => updateInput('email', value)}
      />
      <Input
        value={form.password.value}
        type={form.password.type}
        secureTextEntry={true}
        placeholder="비밀번호"
        placeholderTextColor={'#ddd'}
        onChangeText={value => updateInput('password', value)}
      />

      {confirmPassword()}
      {formHasErrors()}
    </View>
  );
};

const styles = StyleSheet.create({
  errorContainer: {
    marginBottom: 10,
    marginTop: 30,
    padding: 20,
    backgroundColor: '#ee3344',
  },
  errorLabel: {
    color: '#fff',
    fontSize: 15,
    fontWeight: 'bold',
    textAlignVertical: 'center',
    textAlign: 'center',
  },
});

export default AuthForm;

 

 


참고 자료

https://www.inflearn.com/course/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%84%A4%EC%9D%B4%ED%8B%B0%EB%B8%8C-%EC%A4%91%EA%B8%89/dashboard

 

iOS/Android 앱 개발을 위한 실전 React Native - Intermediate - 인프런 | 강의

React Native 기반 모바일 앱 개발을 위한 중급 강의입니다. 프론트엔드의 심화내용 학습 뿐만 아니라 Firebase 기반의 백엔드 내용까지 함께 배우면서, 서버 연동/ 로그인/ 데이터 송수신/ 공공API 활

www.inflearn.com