ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [React] react-select 라이브러리와 onChange event 함수
    프레임워크/React(NextJs) 2021. 6. 22. 19:47

    오늘의 삽질과 허접한 해결방법 .... 많은 조언 부탁드립니다. (_ _)

     

    이 예제는 react 에 redux-saga가 사용된 예제임을 알립니다!!!

     

    저는 Nodejs로 서버만 다뤄보다가 React 공부를 한 시간이 30시간도 안된 초초초초핵초보이기 때문에

    수정방법은.... 일단 실행이 되게 만들었습니다. (ㅎ;ㅎ)

     

    혹시 저와 같은 어려움을 겪고 계신 분들께 도움이 되었으면 좋겠고,

    저도 더 배워서 좋은 코드를 만들고 싶어서 올리게 되었습니다. 많은 조언 부탁드립니다.

    + 저도 더 공부하면서 원인을 알게되면 수정해서 올리도록 하겠습니다 !

     

    '리액트를 다루는 기술'에서 로그인, 회원가입 부분을 구현하기(p.732~p.786)를 따라하고 공부하던중

    Form 중에 select input 태그를 다뤄보고 싶어서 찾아보다가 너무나도 구린 기본 UI select 에 기겁해서 뒷걸음질쳤고....

    아래와 같은 라이브러리를 찾게되었습니다.

     

    https://react-select.com/home

     

    React-Select

    A flexible and beautiful Select Input control for ReactJS with multiselect, autocomplete and ajax support.

    jedwatson.github.io

     

     

    주요 파일 두개만 가져와보자면 아래 두개의 파일입니다.

     

     

    components/auth/AuthForm.js

    import React from 'react';
    import styled from 'styled-components';
    import { Link } from 'react-router-dom';
    import Button from '../common/Button';
    import palette from '../../lib/styles/palette';
    import Select from 'react-select';
    import chroma from 'chroma-js';
    
    /** react-select 옵션 및 설정 코드 */
    const colourOptions = [
      { name: 'color', value: 'ocean', label: 'Ocean', color: '#00B8D9', isFixed: true },
      { name: 'color', value: 'blue', label: 'Blue', color: '#0052CC', isDisabled: true },
      { name: 'color', value: 'purple', label: 'Purple', color: '#5243AA' },
      { name: 'color', value: 'red', label: 'Red', color: '#FF5630', isFixed: true },
      { name: 'color', value: 'orange', label: 'Orange', color: '#FF8B00' },
      { name: 'color', value: 'yellow', label: 'Yellow', color: '#FFC400' },
      { name: 'color', value: 'green', label: 'Green', color: '#36B37E' },
      { name: 'color', value: 'forest', label: 'Forest', color: '#00875A' },
      { name: 'color', value: 'slate', label: 'Slate', color: '#253858' },
      { name: 'color', value: 'silver', label: 'Silver', color: '#666666' }
    ];
    
    const dot = (color = '#ccc') => ({
      alignItems: 'center',
      display: 'flex',
    
      ':before': {
        backgroundColor: color,
        borderRadius: 10,
        content: '" "',
        display: 'block',
        marginRight: 8,
        height: 10,
        width: 10
      }
    });
    
    const colourStyles = {
      control: styles => ({ ...styles, backgroundColor: 'white' }),
      option: (styles, { data, isDisabled, isFocused, isSelected }) => {
        const color = chroma(data.color);
        return {
          ...styles,
          backgroundColor: isDisabled ? null : isSelected ? data.color : isFocused ? color.alpha(0.1).css() : null,
          color: isDisabled ? '#ccc' : isSelected ? (chroma.contrast(color, 'white') > 2 ? 'white' : 'black') : data.color,
          cursor: isDisabled ? 'not-allowed' : 'default',
    
          ':active': {
            ...styles[':active'],
            backgroundColor: !isDisabled && (isSelected ? data.color : color.alpha(0.3).css())
          }
        };
      },
      input: styles => ({ ...styles, ...dot() }),
      placeholder: styles => ({ ...styles, ...dot() }),
      singleValue: (styles, { data }) => ({ ...styles, ...dot(data.color) })
    };
    
    /** react-select 옵션 및 설정 코드 */
    
    
    /** 회원가입 css */
    const AuthFormBlock = styled.div`
      h3 {
        margin: 0;
        color: ${palette.gray[8]};
        margin-bottom: 1rem;
      }
    `;
    
    const StyledInput = styled.input`
      font-size: 1rem;
      border: none;
      border-bottom: 1px solid ${palette.gray[5]};
      padding-bottom: 0.5rem;
      margin-bottom: 0.5rem;
      outline: none;
      width: 100%;
      &:focus {
        color: $oc-teal-7;
        border-bottom: 1px solid ${palette.gray[7]};
      }
      & + & {
        margin-top: 1rem;
      }
    `;
    
    const MarginDiv = styled.div`
      heigth: 0.3rem;
      width: 100%;
      margin-top: 0.5rem;
    `;
    
    const Footer = styled.div`
      margin-top: 2rem;
      text-align: right;
      a {
        color: ${palette.gray[6]};
        text-decoration: underline;
        &:hover {
          color: ${palette.gray[9]};
        }
      }
    `;
    
    const ButtonWidthMarginTop = styled(Button)`
      margin-top: 1rem;
    `;
    
    const textMap = {
      login: '로그인',
      register: '회원가입'
    };
    
    // 에러를 보여줌
    const ErrorMessage = styled.div`
      color: red;
      text-align: center;
      font-size: 0.875rem;
      margin-top: 1rem;
    `;
    
    /** 회원가입 css */
    
    const AuthForm = ({ type, form, onChange, onSubmit, error }) => {
      const text = textMap[type];
      return (
        <AuthFormBlock>
          <h3>{text}</h3>
          <MarginDiv />
          <form onSubmit={onSubmit}>
            <StyledInput
              autoComplete="username"
              name="username"
              placeholder="아이디"
              onChange={onChange}
              value={form.username}
            />
            <StyledInput
              autoComplete="new-password"
              name="password"
              placeholder="비밀번호"
              type="password"
              onChange={onChange}
              value={form.password}
            />
            {type === 'register' && (
              <>
                <StyledInput
                  autoComplete="new-password"
                  name="passwordConfirm"
                  placeholder="비밀번호 확인"
                  type="password"
                  onChange={onChange}
                  value={form.passwordConfirm}
                />
                <MarginDiv />
                <Select
                  styles={colourStyles}
                  className="basic-single"
                  classNamePrefix="select"
                  defaultValue={colourOptions[0]}
                  name="color"
                  options={colourOptions}
                  onChange={onChange}
                />
              </>
            )}
            {error && <ErrorMessage> {error} </ErrorMessage>}
            <ButtonWidthMarginTop purple fullWidth>
              {text}
            </ButtonWidthMarginTop>
          </form>
          <Footer>{type === 'login' ? <Link to="/register">회원가입</Link> : <Link to="/login">로그인</Link>}</Footer>
        </AuthFormBlock>
      );
    };
    
    export default AuthForm;
    

     

     

    containers/auth/RegisterForm.js

    import React, { useEffect, useState } from 'react';
    import { useDispatch, useSelector } from 'react-redux';
    import { changeField, initializeForm, register } from '../../modules/auth';
    import { check } from '../../modules/user';
    import AuthForm from '../../components/auth/AuthForm';
    import { withRouter } from 'react-router-dom';
    
    const RegisterForm = ({ history }) => {
      const [error, setError] = useState(null);
      const dispatch = useDispatch();
     
      const { form, auth, authError, user } = useSelector(({ auth, user }) => ({
        form: auth.register,
        auth: auth.auth,
        authError: auth.authError,
        user: user.user //왜 user.user 는 success 인 것일까..?
      }));
    
    /** 이 함수가 문제의 부분*/
      const onChange = e => {
        console.log(e);
        const { value, name } = e.target;
        
        dispatch(
          changeField({
            form: 'register',
            key: name,
            value
          })
        );
      };
    
      const onSubmit = e => {
        e.preventDefault();
        let { username, password, passwordConfirm, color } = form;
    
        // util 파일로 한번에 공백제거 필요
        username = username.replace(/ /g, '');
        password = password.replace(/ /g, '');
        passwordConfirm = passwordConfirm.replace(/ /g, '');
    
        if ([username, password, passwordConfirm].includes('')) {
          console.log('빈 칸을 모두 입력하세요.');
          setError('빈 칸을 모두 입력하세요.');
          return;
        }
    
        if (password !== passwordConfirm) {
          // TODO: 오류 처리
          console.log('password !== passwordConfirm');
          setError('비밀번호가 일치하지 않습니다.');
    
          dispatch(changeField({ form: 'register', key: 'password', value: '' }));
          dispatch(changeField({ form: 'register', key: 'passwordConfirm', value: '' }));
          return;
        }
        dispatch(register({ username, password, color }));
      };
    
      useEffect(() => {
        dispatch(initializeForm('register'));
      }, [auth, authError, dispatch]);
    
      useEffect(() => {
        if (authError) {
          // 현재 서버에 유저에게 보여줄 메세지를 리턴하도록 구현하였음
          setError(authError.response.data['user_msg']);
          console.log(authError);
          return;
        }
    
        if (auth) {
          console.log('회원가입 성공');
          console.log(auth);
          dispatch(check());
        }
      }, [auth, authError, dispatch]);
    
      useEffect(() => {
        if (user) {
          console.log('check API 성공');
          console.log(user);
          history.push('/'); // 홈 화면으로 이동
        }
      }, [history, user]);
    
      return <AuthForm type="register" form={form} onChange={onChange} onSubmit={onSubmit} error={error} />;
    };
    
    export default withRouter(RegisterForm);
    

     

     

    첫번째 파일 (이하 A ) : components/auth/AuthForm.js

    두번째 파일 (이하 B ) : containers/auth/RegisterForm.js

     

    다른 부분들은 문제가 없었고,

    A파일에서 react-select 라이브러리를 사용한 SELECT 태그에 onChange={onChange}를 하는 순간 문제가 발생하였습니다.

     

    B파일의 onChange에 e.target 이 없다는 error 였습니다.

     

     

    그래서 e 를 console 에 찍어보니..

    이런 데이터가 들어오는 것을 확인할 수 있었습니다...

     

    const colourOptions = [
      { name: 'color', value: 'ocean', label: 'Ocean', color: '#00B8D9', isFixed: true },
      { name: 'color', value: 'blue', label: 'Blue', color: '#0052CC', isDisabled: true },
      { name: 'color', value: 'purple', label: 'Purple', color: '#5243AA' },
      { name: 'color', value: 'red', label: 'Red', color: '#FF5630', isFixed: true },
      { name: 'color', value: 'orange', label: 'Orange', color: '#FF8B00' },
      { name: 'color', value: 'yellow', label: 'Yellow', color: '#FFC400' },
      { name: 'color', value: 'green', label: 'Green', color: '#36B37E' },
      { name: 'color', value: 'forest', label: 'Forest', color: '#00875A' },
      { name: 'color', value: 'slate', label: 'Slate', color: '#253858' },
      { name: 'color', value: 'silver', label: 'Silver', color: '#666666' }
    ];

    그래서 이 데이터는 어디에서 오나 보니 A파일에서 제가 colourOptions로 만들어준 객체배열중에 선택된 값 객체더군요.. onChange={onChange}를 해주면 당연히 event 가 발생하는줄 알았던 저는 뒷통수가 얼얼했습니다.

    (역시 개발에는 당연한게 없구나하는걸 다시한번 깨달았죠.......................특히 js................. 너란녀석..)

     

    아무튼 그래서 저는 B파일의 onChange 를 수정했습니다.

      const onChange = e => {
        let target = e.name === 'color' ? e : e.target;
        const { value, name } = target;
    
        // console.log(value);
        dispatch(
          changeField({
            form: 'register',
            key: name,
            value
          })
        );
      };

     

    가장 먼저 떠오르는 방법으로 우선은 실행되게 만들었습니다.

     

    이렇게 구현한 이유는 다른 input 타입들은 그대로 e.target 을 써야했고,

    select box 만 e 값이 다르게 들어오니, 구분하여 target 변수에 넣어 값을 쓸 수 있도록 했습니다.

     

    제가 좀 더 react 에 능숙했다면

    왜 A파일의 SELECT 태그에서 onChange 를 하면 event 함수가 제대로 들어가지 않는지 알고,

    그 부분을 수정할 수 있었을텐데... 저는 아직 그부분까지 파악을 하지는 못했습니다.

     

    혹시 아시는 분이 있다면 가르침 부탁드립니다.

    감사합니다 :)

    댓글

Designed by Tistory.