본문으로 바로가기

230901 : react ts _ custom select box

category Log/TIL 2023. 9. 2. 02:22


이번에 회원가입 맡으면서 select 박스 여러개를 만들 일이 생겼는데 처음에는 컴포넌트로 빼지도 않았고, select box 외부 영역 클릭시 닫히는 것에 대해 고려하지 않고 만들었다. 최근에 select box를 만들일이 거의 없어서 어떻게 만드는지 까먹었었고, 다시 익숙해지는데까지 시간이 필요했다. 다행히도 select 박스를 이번에 여러번 만들게 되면서 대충 어떤 식으로 만들어야하는지 감이 조금 온다.

 

아래 사용했던 방법을 캡쳐해왔당.......히히

 

다음은 typescript가 적용된 react project에서 지역을 선택하는 select box를 구현한 코드이다.

1. 우선 html 부분을 그려준다.

첨에 닫혀있던 box를 클릭했을 때 dropdown 방식으로 나오게 하고 싶다면, 필요한 html 구성은  다음과 같다.

dropdown 해줄 전체 dropdown_wrapper

dropdown_header > : header 안에는 default text와 선택한 option에 대한 text가 보여질 수 있는 element를 적어준다!

dropdown_body(=dropdown_container)  > : select > options map 메서드로 돌려서 선택지 만들어주기 

 

아래 접힌 곳에 있는 예시는 상위태그 선택시 하위태그가 변하게 만든거라 dropdown box 2개를를 하나의 컴포넌트로 묶어놓았다!. (경상북도 선택시 -> 포항시, 경주시, 등등 뜰 수 있도록! 서울특별시 선택시 -> 강남구, 종로구 뜰 수 있도록!)

 

아래 접힘에 있는 이미지를 보면 보라색, 연두색, 파란색, 빨간색으로 구분해놓았는데 각 구분의 역할?은 다음과 같다.

  보라   : select할 영역을 useRef를 이용하여 선택할 수 있도록 지정해두기

  연두   : useState()를 이용해서 만든 setIsDropMenuOpen() 이라는 함수를 이용해서 dropdown 박스가 열리고 닫힐 수 있도록 관리! (dropdown_header를 선택하면 토글형식으로 드롭박스를 열고 닫을 수 있고, option을 선택해도 닫힐 수 있도록 하는데 사용!) 선택하기 전 기본 상태 메시지와 option 선택했을 때 보여줄 상태메시지를 적을 수 있는 공간을 dropdown header에 만들어주기!

  파랑   : dropbox options를 담아 놓는 containerOPTIONS라는 상수 배열을 이용해서 option들을 map으로 뿌려주는 식으로 생성해준다! 그럼 하나하나 입력 안해도 되고 각 element를 손쉽게 만들 수 있움!! 

  빨강   : 빨간 부분이 map 이용해서 뿌려지는 각각의 option 부분!

 

 

2. 사용방법은  다음과 같다. 아래 접힘 안에 있는 이미지를 참고해달라.

<  dropdown(select) box 외부 영역 클릭시 box 닫기  >

⓵  useRef를 이용해서 내가 선택할 element의 ref에 useRef로 정의한 ref객체를 refernce로 연결해준다. (나는 연결된? 두개의 select box를 만들어야해서 useRef를 두개 정의했다. 똑같은 select box 만들거면 하나로 관리하면 됨)

⓶  이벤트 리스너와 useEffect를 이용해서 해당 select box 컴포넌트에서 select box를 열고 닫을 때 상태변화하는 isDropMenuOpen과 같은 state 가 변화할 때마다 이벤트리스너가 실행되고, 정지될 수 있도록  코드를 짜준다. 나는 dropbox가 열려있고,  ref의 current(최근 선택한 영역)이 useRef로 지정해둔 영역이 아닌 다른 외부 node일 때 select box가 닫히도록 코드를 짰다. 

 

 

<  내부 영역 선택했을 때 dropdown(select) box 변화주기  >

** select box를 컴포넌트로 따로 빼서 관리하기 때문에 select box 상태 자체는 해당 컴포넌트에서 관리하되, 나는 form tag 안에서 selectbox로 선택한값을 바로바로 받아서 넘겨줘야하기 때문에 받은 state값도 select 컴포넌트 외부에서 useState()로 만든 후 setState()함수를 select box 컴포넌트로 넘겨주어 사용해야한다.!!! 

* option 을 클릭했을 때 실행할 함수를 만들어준다! 함수 내부 로직은 다음과 같다.

⓪ 함수 실행했을 때 일단 sido 선택중인지 gugun 선택중인지 if 조건문을 통해서 실행할 부분으로 보내준다.(sido 먼저 선택했다고 가정하고 풀이 설명해보겠움)

⓵ 첫번째 '시/도 영역'을 새로이 선택하기에 앞서(상위영역 dropbox header 클릭시) 하위 영역인 '구/군 영역'은 빈배열로 먼저 reset 되어야한다.

⓶  선택된 option에 sido option을 set 해준다.

⓷  2번에서 set해준건 select box 컴포넌트 내부에서만 저장되는 값이므로 우리가 받아야할 form 컴포넌트 영역에서 변경된 값을 받을 수 있도록 props로 받아온 setState함수를 이용해 form 컴포넌트 영역에 변경된 값(selected sido option)을 알려준다. (이 때 form 컴포넌트에서 같은 selectbox 컴포넌트를 두번 사용하여 sido, gugun 정보를 두쌍 받고 있다면, props로 전달받은 locationType정보를 이용해서 구분하여 set해준다. ---- 이 부분 헷갈리면 완전 아래 다른 이미지에 있는 form 컴포넌트의 state 확인해보세염)

⓸  나는 일단 sido 선택했고, 반영도 해줬기때문에 이쯤되면 일단 select box를 닫아도 됨. (근데 만약에 '전체 sido' 영역을 선택했으면 해당되는 '구/군 영역'이 없기 때문에 '구/군 영역'도 선택사항이 없다는 상태로 reset해줘야함.) (이거는 if조건문으로 해당사항 있으면 다음 step으로 진행되지 않도록 return 시켜주면 됨)

⓹  만약에 '시/도 영역'을 선택했으면 우리가 OPTION 들을 상수로 빼뒀던 배열에 저장되어있는 정보를 이용해서 '구/군 의 OPTION'을 자꾸 업데이트 해주면 됨 !( 시/도 영역이 생각보다 많고, 그 하위 구/군 영역도 많기 때문에 이렇게 업뎃해서 바꽈줘야함 ) 아래 접힌 부분에 있는 코드가 따로 저장되어 있고, 보면 규칙이 있음. 그 규칙에 따라서 이미지에 있는 코드처럼 '구/군 영역'을 재할당해주면 됨

⓺  '구/군 영역'의 options를 갈아끼워줬으니까'구/군 영역'의 select box의 첫번째에는 배열의 첫번째 정보가 오도록 index 0번째를 첫번재 값으로 set해줌 + 당연하게도 그 정보를 form 컴포넌트에도 업뎃해줘야하므로 form으로부터 props로 전달받은 setState를 이용하여 업뎃해주기!

⓻  그리고 이제는 '구/군 영역' select box를 클릭했을 경우!!! if 문을 이용해서 판단하고 '시/도 영역'과 마찬가지로 'selected option 변경 → form 컴포넌트에서 받은 props로 state 업뎃 → dropdown box 닫아주는 함수 실행' 순서로 똑같이 진행해준다.

export const AREA0 = ['전체', '서울', '인천', '대전', '광주', '대구', '울산', '부산', '경기', '강원', '충북', '충남', '전북', '전남', '경북', '경남', '제주'];
export const AREA1 = ['강남구','강동구','강북구','강서구','관악구','광진구','구로구','금천구','노원구','도봉구','동대문구','동작구','마포구','서대문구','서초구','성동구','성북구','송파구','양천구','영등포구','용산구','은평구','종로구','중구','중랑구',
];
export const AREA2 = ['계양구', '남구', '남동구', '동구', '부평구', '서구', '연수구', '중구', '강화군', '옹진군'];
export const AREA3 = ['대덕구', '동구', '서구', '유성구', '중구'];
export const AREA4 = ['광산구', '남구', '동구', '북구', '서구'];
export const AREA5 = ['남구', '달서구', '동구', '북구', '서구', '수성구', '중구', '달성군'];
export const AREA6 = ['남구', '동구', '북구', '중구', '울주군'];
export const AREA7 = ['강서구', '금정구', '남구', '동구', '동래구', '부산진구', '북구', '사상구', '사하구', '서구', '수영구', '연제구', '영도구', '중구', '해운대구', '기장군'];
export const AREA8 = ['고양시','과천시','광명시','광주시','구리시','군포시','김포시','남양주시','동두천시','부천시','성남시','수원시','시흥시','안산시','안성시','안양시','양주시','오산시','용인시','의왕시','의정부시','이천시','파주시','평택시','포천시','하남시','화성시','가평군','양평군','여주군','연천군',
];
export const AREA9 = ['강릉시', '동해시', '삼척시', '속초시', '원주시', '춘천시', '태백시', '고성군', '양구군', '양양군', '영월군', '인제군', '정선군', '철원군', '평창군', '홍천군', '화천군', '횡성군'];
export const AREA10 = ['제천시', '청주시', '충주시', '괴산군', '단양군', '보은군', '영동군', '옥천군', '음성군', '증평군', '진천군', '청원군'];
export const AREA11 = ['계룡시', '공주시', '논산시', '보령시', '서산시', '아산시', '천안시', '금산군', '당진군', '부여군', '서천군', '연기군', '예산군', '청양군', '태안군', '홍성군'];
export const AREA12 = ['군산시', '김제시', '남원시', '익산시', '전주시', '정읍시', '고창군', '무주군', '부안군', '순창군', '완주군', '임실군', '장수군', '진안군'];
export const AREA13 = ['광양시', '나주시', '목포시', '순천시', '여수시', '강진군', '고흥군', '곡성군', '구례군', '담양군', '무안군', '보성군', '신안군', '영광군', '영암군', '완도군', '장성군', '장흥군', '진도군', '함평군', '해남군', '화순군'];
export const AREA14 = ['경산시','경주시','구미시','김천시','문경시','상주시','안동시','영주시','영천시','포항시','고령군','군위군','봉화군','성주군','영덕군','영양군','예천군','울릉군','울진군','의성군','청도군','청송군','칠곡군',
];
export const AREA15 = ['거제시', '김해시', '마산시', '밀양시', '사천시', '양산시', '진주시', '진해시', '창원시', '통영시', '거창군', '고성군', '남해군', '산청군', '의령군', '창녕군', '하동군', '함안군', '함양군', '합천군'];
export const AREA16 = ['서귀포시', '제주시', '남제주군', '북제주군'];

 

 

3. 아래는 css 부분!! 각 element별 핵심 css만 일단 보자!

dropbox header 밑에 options를 바로 모아 보여주려면 이 header와 container(=options부분)를 담고 있는 가장큰 wrapper에 position relative를 주고, 위치를 정해서 띄어줄 container부분에 position absolute를 줘서 일단 해결!

 

header랑 option 각각에 height를 줘서 높이 설정하고, 글자랑 sink 맞추려면 line-height를 height값이랑 똑같이 주면됨

 

그리고 options가 특정 높이보다 더 많아질 때 스크롤 주고 싶으면 option하나가 아닌 options를 담고 있는 container부분에 최대 높이를 명시해주고, overflow: scroll 해주면 됨. 나는 y축 스크롤만 필요해서 쩌렇게 줬는데 필요에 따라 찾아서 사용하시면 됨.

 

 

4. SelectBox 컴포넌트 만들어서 사용할 때 selectbox 컴포넌트에 props로 넘겨줄 것들!

선택해서 받아야할 게 하나밖에 없으면 아래처럼 굳이 key:value 객체형태로 저장안해도 됨. options 0~10중에 특정 값 하나(8)만 받아올거면 걍 const [number, setNumber] = useState(0) 이런식으로 해서 받아올 값이 하나가 되겠고, selectbox props로 전달해줄 건 setNumber밖에 없겠지!!!

근데 나는 관계성을 띄고 있는 'sido랑 gugun'정보를 한번에 가져왔어야했고, 지역선택이 2경우를 들고 와야했기 때문에 아래와같이 '지역1, 지역2'에 관한 정보도 넘겨줘야했고, 받아올 값의 형태도 key:value의 객체형태로 받아왔어야 했음!!!! 

재사용하려면 어쨌든 input값을 어떤걸 어떻게 넣어줄것인가 고민이 필요함.... 넘나 헷갈렸고, 오래걸렸짐나 나 수고함.......흑흑

 

(사실 select box를 컴포넌트로 땔 때 어느 element 구성으로 넘겨줘야할지 고민했는데 최소로 필요한 것들만 들어가게 구성해보았음... 그래서 내가 여기에 올린 방식 사용하려면 아래 접힘이미지 2번째 처럼 원하는 외부 element좀 더 붙여줘야함)