Language/React
React/DropdownSearch 만들기
건담아빠
2022. 12. 1. 09:54
간단한 검색이 가능한 dropdown을 만들려고 한다.
css
.g-dropdown-search-wrap { position: relative; }
.g-dropdown-search-wrap > .search-section { display: flex; justify-content: flex-start; align-items: center; }
.g-dropdown-search-wrap > .search-section input { width: 100%; padding: 12px 8px; border: 1px solid #D9D9D9; }
.g-dropdown-search-wrap > .search-section input:focus { border: 1px solid #25B9B9; }
.g-dropdown-search-wrap > .search-section button { border: 1px solid #D9D9D9; cursor: pointer; border-left-width: 0; }
.g-dropdown-search-wrap > .dropdown-list { display: flex; flex-direction: column; justify-content: center; align-items: flex-start; position: absolute; left: 0; z-index: 10; border-radius: 2px; background: #FFFFFF; cursor: pointer; box-shadow: 0px 3px 6px -4px rgba(0, 0, 0, 0.12), 0px 6px 16px rgba(0, 0, 0, 0.08), 0px 9px 28px 8px rgba(0, 0, 0, 0.05); }
.g-dropdown-search-wrap > .dropdown-list > .option { width: 100%; height: 32px; padding: 5px 12px; color: rgba(0, 0, 0, 0.65); font-weight: 400; font-size: 14px; line-height: 22px; }
.g-dropdown-search-wrap > .dropdown-list > .option:hover { background: #4db8ff; color: white; }
부모 compoent
import React, { Component } from 'react';
...
class ListBase extends Component {
...
state = {
...
dropdown: {
key: '',
text: '',
show: false,
items: [],
},
};
refTab1 = React.createRef();
refTab2 = React.createRef();
refTab3 = React.createRef();
init = () => {
this.fetchDropdown({ key: this.state.dropdown.key, text: this.state.dropdown.text });
};
...
fetchDropdown = (item) => {
this.setDelay(() => {
Loading.enable(false);
const params = { SEARCH_TEXT: item.text, HOST_MEMBER_ID: this.hostMemberId };
Request.get('/contracts/simple?' + JSON.stringify(params)).then((response) => {
Loading.enable(true);
const dropdownItems = [];
for (const item of response.DATA) {
// dropdownItems.push({ key: item.CONTRACT_ID, text: `${item.GUEST_NAME} (${item.HP_NO})` });
dropdownItems.push({ key: item.CONTRACT_ID, text: `${item.GUEST_NAME}` });
}
const theDropdown = { ...this.state.dropdown };
theDropdown.items = dropdownItems;
this.setState({
dropdown: theDropdown,
});
});
}, 500);
};
dropdownSearchTimer = null;
setDelay = (callback, ms) => {
clearTimeout(this.dropdownSearchTimer);
this.dropdownSearchTimer = setTimeout((args) => {
callback.apply(this, args);
}, ms || 0);
};
fetchAll = () => {
this.refTab1.current.fetchAll();
this.refTab2.current.fetchAll();
this.refTab3.current.fetchAll();
};
render() {
...
return (
<div id="gundam-container">
<div className="page-header">
...
</div>
<div className="search-wrap">
<div className="search-form">
<div className="row" style={{ marginBottom: '16px' }}>
<dl>
<dt>
<i className="search-line-gray85-16"></i>
통합검색
</dt>
<dd>
<DropdownSearch
class="g-dropdown-search-wrap"
styles={{
height: '40px',
// height: 'auto',
}}
icon={{
class: 'search-line-gray85-16',
width: '40px',
height: '40px',
}}
placeholder="계약자명, 휴대폰번호 입력"
selectedKey={this.state.dropdown.key}
selectedText={this.state.dropdown.text}
show={this.state.dropdown.show}
onDropdownSearch={(item) => {
const theDropdown = { ...this.state.dropdown };
theDropdown.key = item.key;
theDropdown.text = item.text;
this.setState({ dropdown: theDropdown }, () => {
this.fetchDropdown(item);
});
}}
onSearch={(item) => {
const theDropdown = { ...this.state.dropdown };
theDropdown.key = item.key;
theDropdown.text = item.text;
this.setState({ dropdown: theDropdown }, () => {
this.fetchAll();
});
}}
items={this.state.dropdown.items}
/>
</dd>
</dl>
</div>
</div>
</div>
...
</div>
);
}
}
export default wrapWithBase(ListBase);
DropdownSearch
import React, { useEffect, useState, useRef, useCallback } from 'react';
const DropdownSearch = (props) => {
const [data, setData] = useState({
key: props.selectedKey ?? '',
text: props.selectedText ?? '',
show: props.show ?? false,
});
// useEffect(() => {
// console.log('마운트 될 때만 실행');
// }, []);
// useEffect(() => {
// console.log('렌더링 될 때마다 실행');
// });
// useEffect(() => {
// console.log('data가 업데이트 될 때마다 실행');
// console.log('Do something after data has changed', data);
// }, [data]);
useEffect(() => {
// data.key 가 변경되었을때 호출
if (data.show === true) {
props.onSearch({ key: data.key, text: data.text });
}
}, [data.key]);
const classWrap = props.class ?? 'g-dropdown-search-wrap';
const styles = props.styles;
const height = styles?.height ?? 'auto';
const placeholder = props.placeholder ?? '검색어를 입력하세요.';
const resultWrapStyle = props.icon && { width: 'calc(100% - 40px)' };
const handleChange = (key) => {
setData((prevState) => ({
...prevState,
...key,
}));
};
return (
<>
<div className={classWrap}>
<div className="search-section">
<input
// ref={searchInput}
placeholder={placeholder}
style={{ height: height }}
value={data.text ?? ''}
onChange={(e) => {
handleChange({ text: e.target.value });
}}
onClick={() => {
handleChange({ show: !data.show });
}}
onKeyUp={(e) => {
if (e.key === 'Enter') {
props.onSearch({ key: '', text: data.text });
handleChange({ show: false });
} else {
props.onDropdownSearch({ key: '', text: e.target.value });
handleChange({ show: true });
}
}}
onBlur={(e) => {
if (e.nativeEvent.explicitOriginalTarget && e.nativeEvent.explicitOriginalTarget === e.nativeEvent.originalTarget) {
return;
}
if (data.show) {
setTimeout(() => {
handleChange({ show: false });
}, 200);
}
}}
/>
{(() => {
if (props.icon) {
const iconClass = props.icon.class;
const iconWidth = props.icon.width;
const iconHeight = props.icon.height;
return (
<>
<button
style={{ width: iconWidth, height: iconHeight }}
onClick={() => {
props.onSearch({ key: data.key, text: data.text });
}}
>
<i className={iconClass}></i>
</button>
</>
);
}
})()}
</div>
<ul className="dropdown-list" style={resultWrapStyle} hidden={!data.show}>
{props.items.map((item, index) => (
<li
key={item.key}
className="option"
onClick={(e) => {
handleChange({ key: item.key, text: item.text });
// useEffect 에서 검색처리
// props.onSearch({ key: index, text: item });
}}
>
{item.text}
</li>
))}
</ul>
</div>
</>
);
};
export default DropdownSearch;
참조)
https://codepen.io/quafoo/pen/jONdwWG
React Dropdown (onBlur)
Use onBlur to handle background click...
codepen.io