ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • React/DropdownSearch 만들기
    Language/React 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

     

    댓글

Designed by Tistory.