React 16장 - Autocomplete 기능
포스트
취소

React 16장 - Autocomplete 기능

React

Autocomplete

  • 갖고 있는 정보에 일치하는 문자를 입력했을 경우, 아래에 문자와 일치하는 정보를 나타내는 기능 구현

컴포넌트 짜기

  • 입력창을 갖고 있는 컴포넌트와 입력을 받았을 때 하위에 자동완성 기능을 나타낼 컴포넌트로 나눈다.
  • 컴포넌트 안에 요소를 작성한다.
1
2
3
4
5
6
return (
    <InputContainer>
        <input />
    </InputContainer>
    <DropDown />
)

Styled-Components

  • 기존의 정보값을 갖고 있을 배열을 임의로 작성해준다.
  • InputContainer에 div를 만들어 속성을 정한다.
    • focus-withininput을 클릭했을 때 발생
  • 상태값에 따라 클래스를 추가하고 빼내어 속성값을 부여한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
const autoValue = [
  "apple",
  "angle",
  "adios",
  "banana",
  "bare",
  "bread",
  "candy",
  "car",
  "drop",
  "down",
  "function",
];

const boxShadow = "0 4px 6px rgb(32 33 36 / 28%)";
const inactiveBorderRadius = "1rem 1rem 1rem 1rem";

export const InputContainer = styled.div`
  margin-top: 8rem;
  background-color: #ffffff;
  display: flex;
  felx-direction: row;
  padding: 1rem;
  border: 1px solid rgb(223, 225, 229);
  border-radius: ${inactiveBorderRadius};
  z-index: 3;
  box-shadow: 0;

  &:focus-within {
    box-shadow: ${boxShadow};
  }

  > input {
    flex: 1 0 0;
    background-color: transparent;
    border: none;
    margin: 0;
    padding: 0;
    outline: none;
    font-size: 16px;
  }

  > div.delete-button {
    cursor: pointer;
  }
`;

export const DropDownContainer = styled.ul`
    background-color: #ffffff;
    display: block;
    margin-left: auto;
    margin-right: auto;
    list-styled-type: none;
    margin-block-start: 0;
    margin-block-end: 0;
    margin-inline-start: 0;
    margin-inline-end: 0;
    padding-inline-start: 0p;
    margin-top: -1px;
    padding: 0.5rem 0;
    border: 1px solid rgb(223, 225, 229);
    border-radius: 0 0 1rem 1rem;
    box-shadow: ${boxShadow}
    z-index: 3;

    > li {
        padding: 0 1rem;
    }

    > li:hover {
        background-color: rgb(223,225,229);
    }

    &.select {
        background-color: rgb(223,225,229);
    }
`;

기능 구현

상태값 작성

  • 초기값은 기존에 작성된 autoValue배열을 갖는다.
  • hasTextinput창에 값이 입력되었는지를 확인한다.
  • inputValue는 배열에 input에 입력된 값을 추가하기 위해, input에 작성된 value를 의미한다.
  • options는 초기에 작성한 autoValue값을 의미한다.
  • selectedOption는 우리가 선택한 옵션에 따른 index를 가르킨다.
  • useEffect를 사용하여 input에 입력한 값이 빈 배열이 아닐 때, 기존에 갖고있는 배열과 중복되지 않는다면 값을 추가하기 위해 inputValue의 상태값이 변경될 때마다 새롭게 렌더링한다.
    • push를 이용하면 기존에 갖고있는 배열의 주소값이 변하지 않아 리액트에서 상태의 변화를 알아차리지 못 하기 때문에 filter,mpa,...을 사용하여 추가한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
const [hasText, setHasText] = useState(false);
const [inputValue, setInputValue] = useState("");
const [options, setOptions] = useState(autoValue);
const [selectedOption, setSelectedOption] = useState(0);

useEffect(() => {
  if (inputValue === "") {
    setHasText(false);
  }
  if (inputValue !== "") {
    setOptions(autoValue.filter((option) => option.includes(inputValue)));
  }
}, [inputValue]);

컴포넌트에 이벤트 핸들러 부여

  • 값을 입력하거나, 자동완성을 선택하거나, 삭제버튼을 눌렀을 때에 발생할 이벤트를 작성한다.
  • handleInputChange함수는 이벤트를 발생시킨 요소의 값을 그대로 받아 기존의 배열에 추가하는 역할을 하며, setHasText상태를 true로 만들어 input 값의 유무를 설정한다.
  • handleDropDownClick함수는 우리가 선택한 자동완성된 값을 input요소의 value로 넣어주는 역할을 한다.
  • handleDeleteButtonClick함수는 입력한 input창의 value값을 공백으로 만들어, 입력한 값을 삭제해주는 역할을 한다.
  • handleKeyUp함수는 input을 작성한 상태에서 자동완성된 값을 선택해 주기 위해 실행되며, ArrowDown은 화살표 아래 키를 의미하며, ArrowUp은 화살표 위 키를 눌렀을 때에 index에 값을 추가하거나 빼서 index값을 설정해준다.
    • 작성 후 Enter를 누른다면 handleDropDown함수로 전달인자를 넘겨 기존 배열의 index를 선택한다.
    • 선택 후에는 setSelectedOption을 0으로 만들어, 0번째 index값을 갖게 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const handleInputChange = (event) => {
  setInputValue(event.target.value);
  setHasText(true);
};

const handleDropDownClick = (clickedOption) => {
  setInputValue(clickedOption);
};

const handleDeleteButtonClick = () => {
  setInputValue("");
};

const handleKeyUp = (event) => {
  if (event.key === "ArrowDown" && selectedOption < options.length - 1) {
    setSelectedOption(selectedOption + 1);
  }
  if (event.key === "ArrowUp" && selectedOption > 0) {
    setSelectedOption(selectedOption - 1);
  }
  if (event.key === "Enter") {
    handleDropDownClick(options[selectedOption]);
    setSelectedOption(0);
  }
};

코드

  • 적절한 요소에 이벤트 핸들러를 부여한다.
  • input 요소에 입력된 값을 뜻하는 valueinputValue인 상태값이다.
    • input에 값이 입력되면 handleInputChange함수가 실행되는데, 실행되는 함수는 input에 입력된 value값으로 상태값이 변한다.
      • 함수의 실행 형태로 전달하면 안된다.
      • 키가 입력됐을 때, 키를 판별하여 자동완성의 index를 구하기 위해 selectedOption값이 변경된다.
    • delete-button을 누를 때, handleDelelteButtonClick 함수를 실행시키는데, 입력된 값을 초기화한다.
    • 자동완성 기능을 가진 DropDown 컴포넌트는 DropDownContainer 컴포넌트를 갖고 있으며, 기존 값을 갖고 있는 배열 options, 선택한 index의 값에 클래스를 부여할 selectedOption을 인자로 넘겨준다..
      • DropDownContainer 컴포넌트는 li요소에 options의 각각의 값과 index를 갖고, optionhandleComboBox가 가진 handleDropDownClick함수로 전달인자를 넘겨주어 선택한 option요소를 input창에 넣어준다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
import {useState,useEffect} from "react"
import styled from "styled-components"

const autoValue = [
    "apple",
    "angle",
    "adios",
    "banana",
    "bare",
    "bread",
    "candy",
    "car",
    "drop",
    "down",
    "function"
]

const boxShadow = "0 4px 6px rgb(32 33 36 / 28%)";
const inactiveBorderRadius = "1rem 1rem 1rem 1rem";

export const InputContainer = styled.div`
    margin-top: 8rem;
    background-color: #ffffff;
    display: flex;
    felx-direction: row;
    padding: 1rem;
    border: 1px solid rgb(223, 225, 229);
    border-radius: ${inactiveBorderRadius};
    z-index: 3;
    box-shadow: 0;

    &:focus-within {
        box-shadow: ${boxShadow};
    }

    > input {
        flex: 1 0 0;
        background-color: transparent;
        border: none;
        margin: 0;
        padding: 0;
        outline: none;
        font-size: 16px;
    }

    > div.delete-button {
        cursor: pointer;
    }
`

export const DropDownContainer = styled.ul`
    background-color: #ffffff;
    display: block;
    margin-left: auto;
    margin-right: auto;
    list-styled-type: none;
    margin-block-start: 0;
    margin-block-end: 0;
    margin-inline-start: 0;
    margin-inline-end: 0;
    padding-inline-start: 0p;
    margin-top: -1px;
    padding: 0.5rem 0;
    border: 1px solid rgb(223, 225, 229);
    border-radius: 0 0 1rem 1rem;
    box-shadow: ${boxShadow}
    z-index: 3;

    > li {
        padding: 0 1rem;
    }

    > li:hover {
        background-color: rgb(223,225,229);
    }

    &.select {
        background-color: rgb(223,225,229);
    }
`

export const Autoncomplete = () => {
    const [hasText, setHasText] = useState(false);
    const [inputValue, setInputValue] = useState("");
    const [options, setOptions] = useState(autoValue)
    const [selectedOption, setSelectedOption] = useState(0)

    useEffect(()=>{
        if(inputValue === ""){
            setHasText(false);
        }
        if(inputValue !== ""){
            setOptions(
                autoValue.filter((option)=>option.includes(inputValue))
            )
        }
    },[inputValue])


    const handleInputChange = (event) => {
        setInputValue(event.target.value);
        setHasText(true);
    }

    const handleDropDownClick = (clickedOption) => {
        setInputValue(clickedOption);
    }

    const handleDeleteButtonClick = () => {
        setInputValue("")
    }

    const handleKeyUp = (event) => {
        if(event.key === "ArrowDown" && selectedOption < options.length - 1){
            setSelectedOption(selectedOption + 1);
        }
        if(event.key === "ArrowUp" && selectedOption > 0){
            setSelectedOption(selectedOption - 1);
        }
        if(event.key === "Enter"){
            handleDropDownClick(options[selectedOption])
            setSelectedOption(0);
        }
    }

    return (
        <div>
            <InputContainer>
                <input type="text"
                value={inputValue}
                onChange={(event)=>{
                    handleInputChange(event);
                }}
                onKeyUp={(event)=>{
                    handleKeyUp(event)
                }}/>
                <div className="delete-button" onClick={handleDeleteButtonClick} />
            </InputContainer>
            {options.length && hasText ? (
                <DropDown
                options={options
                handleComboBox={handleDropDownClick}
                selectedOption={selectedOption}
                }
            />) : null}
        </div>
    )
}

export const DropDown = ({options, handleComboBox, selectedOption})=>{
    return (
        <DropDownContainer>
            {options.map(option,index)=>{
                return (
                    <li
                        key={index}
                        onClick={()=>handleComboBox(option)}
                        className={selectedOption === index ? "select" : ""}
                    >
                        {option}
                    </li>
                )
            }}
        </DropDownContainer>
    )
}

stories 작성

  • 컴포넌트명과 stories.js 를 작성하면 컴포넌트의 stories로 인식한다.
  • Tag 컴포넌트를 불러온다.
  • 기본값의 titleExample의 폴더 안에 Tag 컴포넌트를 의미한다.
  • 기본값의 component는 Tag 컴포넌트를 지칭한다.
  • stories는 storybook
  • stories 설명보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React from "react";
import { Tag } from "./components/Tag.js";

export default {
  title: "Example/Tag",
  component: Tag,
};

const Template = (args) => <Tag {...args} />;

export const Primary = Template.bind({});
Primary.args = {
  primary: true,
  label: "Tag",
};

stories 작성

  • 컴포넌트명과 stories.js 를 작성하면 컴포넌트의 stories로 인식한다.
  • Autocomplete 컴포넌트를 불러온다.
  • 기본값의 titleExample의 폴더 안에 Autocomplete 컴포넌트를 의미한다.
  • 기본값의 component는 Autocomplete 컴포넌트를 지칭한다.
  • stories는 storybook
  • stories 설명보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React from "react";
import { Autocomplete } from "./components/Autocomplete.js";

export default {
  title: "Example/Autocomplete",
  component: Autocomplete,
};

const Template = (args) => <Autocomplete {...args} />;

export const Primary = Template.bind({});
Primary.args = {
  primary: true,
  label: "Autocomplete",
};
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.