React

[JSCODE] React 3회차 미션

sangchu 2023. 2. 17. 23:06

학습 키워드

props, state, event handling, useState, useEffect

 

미션 

저번 미션을 통해 만든 회원가입 폼을 학습 키워드 기반으로 다음과 같이 기능을 추가해야 한다.

  • 이메일, 비밀번호, 비밀번호체크, 이름, 나이를 useState로 작성하고, input 컴포넌트에서 이벤트를 통해 state를 변경
  • useEffect를 사용해 비밀번호 재확인이 올바르지 않으면 올바르지 않다는 문구 추가

 

심화 미션

  • 필수 항목을 입력하지 않았을 때와 비밀번호 재확인이 올바르지 않을 때 가입하기 버튼이 클릭할 수 없도록 비활성화
  • 클릭했을 때 폼 내용이 모두 지워지는 초기화 버튼 만들기

 

결과물

처음 화면

 

모든 입력란을 올바르게 입력했을 경우 - 가입하기 버튼 활성화

 

가입하기 버튼을 눌렀을 때 alert가 뜨며, 콘솔에 유저 정보가 보이도록 함

 

비밀번호 재확인이 비밀번호와 같지 않은 경우

 

코드

먼저, 클래스 시간 때 멘토님이 몇가지 팁을 알려주셔서 그것에 대해 적용을 했다.

멘토님은 파일 확장자를 js가 아닌 jsx로 쓰시는데 이는 jsx로 썼다는 것을 구분하기 위해 사용한다고 하셨다. 

js로 사용할 때의 예는, 서버와 통신할 때가 있으면 api 폴더를 만들어서 사용하는데 jsx문법을 사용하지 않으므로 파일 확장명을 js로 설정한다고 한다.

app.jsx에서는 보통 뷰와 관련된 코드들을 작성하지 않고 라우팅 관련 코드들을 쓴다고 한다.

그 외에도 pages폴더, components 폴더 등 react의 디렉토리 구조에 대해서 언급하셔서 그에 맞게 수정했다.

내가 사용했던 css 프레임워크인 styled-components 코드들도 따로 styles 폴더를 만들어 분리해주었다.

 

SignForm.js 

import { useState, useEffect } from "react";
import InputGroup from "../components/InputGroup";
import * as style from "../styles/SignFormStyle";

const SignForm = () => {
  const [input, setInput] = useState({
    email: "",
    password: "",
    repassword: "",
    username: "",
    age: "",
  });
  const [isPasswordMatch, setIsPasswordMatch] = useState("");
  const [isNotCorrect, setIsNotCorrect] = useState(true);

  useEffect(() => {
    if (input.repassword && input.password !== input.repassword) {
      setIsPasswordMatch("비밀번호가 일치하지 않습니다");
    } else {
      setIsPasswordMatch("");
    }
  }, [input.password, input.repassword]);

  useEffect(() => {
    if (
      !isPasswordMatch &&
      input.email &&
      input.password &&
      input.repassword &&
      input.username
    ) {
      setIsNotCorrect(false);
    } else {
      setIsNotCorrect(true);
    }
  }, [
    isPasswordMatch,
    input.email,
    input.password,
    input.repassword,
    input.username,
  ]);

  const onChangeInput = (e) => {
    setInput({
      ...input,
      [e.target.name]: e.target.value,
    });
  };

  const onSubmit = (e) => {
    e.preventDefault();
    console.log(
      `이메일: ${input.email}, 비밀번호: ${input.password},
      비밀번호 재입력: ${input.repassword}, 이름: ${input.username}, 나이: ${input.age}`
    );
    alert(`${input.username}님 환영합니다!`);
  };

  const onClickResetButton = (e) => {
    setInput({
      email: "",
      password: "",
      repassword: "",
      username: "",
      age: "",
    });
  };

  return (
    <>
      <style.Form onSubmit={onSubmit}>
        <style.Header>회원가입</style.Header>
        <InputGroup
          title="이메일"
          type="email"
          placeholder="이메일을 입력하세요"
          name="email"
          isRequired
          onChange={onChangeInput}
        />
        <InputGroup
          title="비밀번호"
          type="password"
          placeholder="비밀번호을 입력하세요"
          name="password"
          isRequired
          onChange={onChangeInput}
        />
        <InputGroup
          title="비밀번호 재확인"
          type="password"
          placeholder="비밀번호을 입력하세요"
          name="repassword"
          isRequired
          onChange={onChangeInput}
          message={isPasswordMatch}
        />
        <InputGroup
          title="이름"
          type="text"
          placeholder="이름을 입력하세요"
          name="username"
          isRequired
          onChange={onChangeInput}
        />
        <InputGroup
          title="나이"
          type="number"
          placeholder="나이를 입력하세요"
          name="age"
          onChange={onChangeInput}
        />
        <style.SubmitButton disabled={isNotCorrect}>
          가입하기
        </style.SubmitButton>
        <style.ResetButton type="reset" onClick={onClickResetButton}>
          초기화
        </style.ResetButton>
      </style.Form>
    </>
  );
};

export default SignForm;

event 객체에서 event.target.name은 해당 인풋의 name을 가리킨다. 이 값을 사용해서 input 여러개 다룰 때 위와같이 유용하게 다룰 수 있다. 

 

InputGroup.js

import * as style from "../styles/InputGroupStyle";

const InputGroup = ({
  title,
  name,
  type,
  placeholder,
  isRequired,
  onChange,
  message,
}) => {
  return (
    <>
      <style.Title>
        <label htmlFor={name}>{title}</label>
        <style.Essential>
          {isRequired && (
            <sup>
              필수 <style.Aster>*</style.Aster>
            </sup>
          )}
        </style.Essential>
      </style.Title>
      <style.Input
        type={type}
        name={name}
        id={name}
        placeholder={placeholder}
        required={isRequired}
        onChange={onChange}
      />
      {message && (
        <style.PasswordNotMatchMsg>{message}</style.PasswordNotMatchMsg>
      )}
    </>
  );
};
export default InputGroup;

 

 

마무리

개념을 학습하고 직접 구현해보는 것은 정말 어려우면서 필요한 과정인 것 같다. 

이번 미션도 역시 저번 미션처럼 해당 기능들을 사용하는 데에 어색해서 초반에 코드 작성에 어려움을 느꼈다. 그래도 완성시키는 과정에서 사용법을 조금씩 알아갈 수 있었다.

 

완성시키고 보니 코드가 너무 길어서 보기 안좋다는 느낌이 들어 styled-components로 css를 작성한 것들을 따로 파일로 분리하는 방법을 찾아서 분리했다. useInput 부분도 처음에 개별적으로 하나하나 썼는데 코드가 너무 길어져서 하나의 객체로 만들어서 작성했다. 

그리고 변수명 짓는게 참 어려운 것 같다. 변수명 짓는 규칙에 대해서도 좀 찾아보고 정리해둬야할 것 같다.

 

그리고 이번 미션과는 외람된 이야기지만..

클래스 시간 때 멘토님께서 완벽주의 성향에 대해서 말씀해주셨는데 이는 항상 나의 걱정거리였기에 해주신 조언들로부터 많은 도움을 받았다.

뭘 먼저 하든지 정답은 없고, 하던걸 하다가 어떤 것에 대해 부족함을 느끼면 그때 다시해도 늦지 않다고 하셨다.

무엇을 시작했을 때 처음부터 100%을 하려면 너무 오래걸리고 비효율적이라고 하셨다. 항상 뭘 한다면 그걸 열심히하고, 시간 여유가 있을 때마다 부족한 점을 메꾸면서 다음공부를 이어서 하라는 조언을 해주셨다.

찝찝함에 계속 다음 단계로 넘어가지 못하고 기본에만 머물고 있는 나에게 필요한 조언이었다.. 바로 해결하기엔 어렵겠지만 의식적으로 노력해봐야겠다!

 

궁금한 점

1. 가입하기 버튼을 눌렀을 때 e.preventDefault()를 사용해야 하는 이유가 궁금합니다.

찾아보니 기본 동작(값 전송, 새로고침)을 실행하지 않도록 지정하는 것 같은데, 지금 이 기능에서 이게 왜 필요한지 궁금합니다! 

 

2. 아래와 같이 이전에 썼던 기록을 클릭해서 작성하면, 해당 input은 파란 배경색을 띄고, state에 값을 저장하지 않는 현상이 보입니다(콘솔 참고). 근데 맨 처음에 렌더링 됐을 때는 정상적으로 동작하는데, 초기화 버튼을 누르고 실행하면 위 문제가 발생합니다.. 혹시 짐작되는 이유가 있으실까요?😭

 

 

 

'React' 카테고리의 다른 글

useState를 이용해 단위변환 기능 만들기  (0) 2023.02.18
state, setState  (0) 2023.02.18
[JSCODE] React 2회차 심화미션  (1) 2023.02.14
[JSCODE] React 2회차 미션  (2) 2023.02.14
React의 기본, JSX  (0) 2023.02.11