-
[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 에 기겁해서 뒷걸음질쳤고....
아래와 같은 라이브러리를 찾게되었습니다.
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 함수가 제대로 들어가지 않는지 알고,
그 부분을 수정할 수 있었을텐데... 저는 아직 그부분까지 파악을 하지는 못했습니다.
혹시 아시는 분이 있다면 가르침 부탁드립니다.
감사합니다 :)
'프레임워크 > React(NextJs)' 카테고리의 다른 글
[React] react-hook-form(feat. typescript)으로 Input 컴포넌트 만들어보기 (0) 2022.09.06 [React] webpack 설정 (실시간 개발 모드 관련) (0) 2022.09.03 [React] .env (환경변수파일) - 'create-react-app'과 'webpack 빌드' 프로젝트에서의 각 설정 및 사용 방법 (0) 2022.09.03 [React] React + typeScript + webpack 설정으로 프로젝트 만들기 (0) 2022.06.09 [Recoil] Next.js + Recoil + typescript 적용하기 (0) 2022.05.29