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>
                  &nbsp;&nbsp;&nbsp;통합검색
                </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