React 17장 - ClickToEdit 기능
포스트
취소

React 17장 - ClickToEdit 기능

React

ClickToEdit

  • 값을 입력했을 때에 정해진 요소의 값을 입력한 값으로 바꿔주는 기능 구현

컴포넌트 짜기

  • 입력할 컴포넌트와 입력을 출력할 컴포넌트로 나눈다.
  • 나머지는 styled-component로 감싸준다.
1
2
3
4
5
6
7
8
9
10
11
12
13
return (
  <>
    <InputView>
      <MyInput />
    </InputView>
    <InputView>
      <MyInput />
    </InputView>
    <InputView>
      <MyInput />
    </InputView>
  </>
);

Styled-Components

  • InputBox, InputEdit, InputView 컴포넌트로 하위 요소들을 정렬해준다.
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
export const InputBox = Styled.div`
    text-align: center;
    display: inline-block;
    width: 150px;
    height: 30px;
    border: 1px #bbb dashed;
    border-radius: 10px;
    margin-left: 1rem;
`;

export const InputEdit = styled.div`
  text-align: center;
  display: inline-block;
  width: 150px;
  height: 30px;
`;

export const InputView = styled.div`
  text-align: center;
  align-items: center;
  margin-top: 3rem;

  div.view {
    margin-top: 3rem;
  }
`;

기능 구현

상태값 작성

  • useRef로 요소를 선택한다.
  • isEditMode로 값을 수정할 수 있는 상태를 만든다.
  • newValue로 입력될 값을 지정한다.
  • useEffect를 사용하여 isEditMode,value의 상태값이 변할 때마다 re-render 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
const inputEl = useRef(null);
const [isEditMode, setEditMode] = useState(false);
const [newValue, setNewValue] = useState(value);

useEffect(() => {
  if (isEditMode) {
    inputEl.current.focus();
  }
}, [isEditMode]);

useEffect(() => {
  setNewValue(value);
}, [value]);

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

  • handleClick함수로 입력하고 있는 상태를 알려주기 위해 setEditModetruefalse로 바꿔주는 역할을 한다.
  • handleBlur함수로 마우스의 선택이 벗어났을 때 발생시키며, 마우스가 벗어난 상태를 저장하고 setEditMode(false) 입력한 값을 newValue의 상태로 바꾼다.
  • handleInputChange함수로 input에서 입력한 value값을 newValue에 저장한다.
1
2
3
4
5
6
7
8
9
10
11
12
const handleClick = () => {
  setEditMode(!isEditMode);
};

const handleBlur = () => {
  setEditMode(false);
  handleValueChange(newValue);
};

const handleInputChange = (e) => {
  setNewValue(e.target.value);
};

코드

  • InputBox 컴포넌트는 div를 갖고, InputEdit 컴포넌트는 input요소를, InputView 컴포넌트는 div를 갖는다.
  • ClickToEdit 컴포넌트를 먼저 보면, ClickToEdit 컴포넌트는 InputView와 MyInput 두 가지 컴포넌트를 갖고 있다.
  • InputView 컴포넌트는 div 요소를 갖고 MyInput 컴포넌트는 value값과 handleValueChange 함수를 인자로 받는다.
  • MyInput 컴포넌트 안의 handleValueChange 함수는 e를 인자로 받는데, 이 인자는 ClickToEdit 컴포넌트에서 인자로 받은 newValue => setName(newValue)이다.
  • handleValueChange 함수는 setNewValue를 통해 MyInput 컴포넌트의 newValue값을 ClickToEdit 컴포넌트의 MyInput으로 전달받은 입력값으로 상태를 변경한다.
  • useEffect를 통해 re-render한다.
  • ClickToEdit 컴포넌트의 MyInput 컴포넌트로 전달받은 value 인자는, 기존의 name값을 뜻하는데, MyInput 컴포넌트의 newValuename을 초기값으로 갖고 있다.
  • 이제 MyInput 컴포넌트를 들여다보면 isEditMode가 거짓일 때, span태그를 보여주고 newValue를 갖고 있다.
    • span태그의 newValue는 초기값인, ClickToEdit 컴포넌트로부터 받은 valuename이다.
  • span태그를 클릭 시 handleClick 함수를 실행시킨다.
    • handleClick 함수는 setEditMode를 통해 isEditModetrue값으로 바꾸는데,useEffect에 의해 isEditMode가 참이기 때문에 inputEl로 설정해 준 input태그를 focus해준다.
  • isEditModetrue 값으로 바뀌면 return문을 통해 InputEdit 컴포넌트를 갖게 된다.
  • InputEdit 컴포넌트는 input태그를 갖고 있으며, input태그 안에 값을 입력할 때마다 onChange={handleInputChange}가 실행되어, handleInputChange 함수가 newValue상태값을 setNewValue를 통해 event를 발생시킨 현재의 targetvalue값으로 바꿔준다.
  • input태그가 아닌 다른 곳을 클릭할 시, focus가 풀리면서 이벤트 핸들러인 onBlur가 실행되고 handleBlur함수를 실행시킨다.
    • handleBlur에 의해 isEditMode의 상태는 false로 바뀌고 ClickToEdit 컴포넌트의 handleValueChange를 역으로 실행시켜 우리가 입력한 값을 newValue의 값으로 갖게 한다.
    • isEditMode의 상태가 false로 바뀌었기 때문에 다시 span태그를 생성하여 입력할 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
import { useState, useEffect, useRef } from "react";
import styled from "styled-components";

export const InputBox = styled.div`
  text-align: center;
  display: inline-block;
  width: 150px;
  height: 30px;
  border: 1px #bbb dashed;
  border-radius: 10px;
  margin-left: 1rem;
`;

export const InputEdit = styled.input`
  text-align: center;
  display: inline-block;
  width: 150px;
  height: 30px;
`;

export const InputView = styled.div`
  text-align: center;
  align-items: center;
  margin-top: 3rem;

  div.view {
    margin-top: 3rem;
  }
`;

export const MyInput = ({ value, handleValueChange }) => {
  const inputEl = useRef(null);
  const [isEditMode, setEditMode] = useState(false);
  const [newValue, setNewValue] = useState(value);

  useEffect(() => {
    if (isEditMode) {
      inputEl.current.focus();
    }
  }, [isEditMode]);

  useEffect(() => {
    setNewValue(value);
  }, [value]);

  const handleClick = () => {
    setEditMode(!isEditMode);
  };

  const handleBlur = () => {
    setEditMode(false);
  };

  const handleInputChange = (e) => {
    setNewValue(e.target.value);
  };

  return (
    <InputBox>
      {isEditMode ? (
        <InputEdit
          type="text"
          value={newValue}
          ref={inputEl}
          onBlur={handleBlur}
          onChange={handleInputChange}
        />
      ) : (
        <span onClick={handleClick}>{newValue}</span>
      )}
    </InputBox>
  );
};

const cache = {
  name: "김코딩",
  age: 20,
};

export const ClickToEdit = () => {
  const [name, setName] = useState(cache.name);
  const [age, setAge] = useState(cache.age);

  return (
    <>
      <InputView>
        <label>이름</label>
        <MyInput
          value={name}
          handleValueChange={(newValue) => setName(newValue)}
        />
      </InputView>
      <InputView>
        <label>나이</label>
        <MyInput value={age} handleValueChange={(newAge) => setName(newAge)} />
      </InputView>
      <InputView>
        <div className="view">
          이름{name}나이{age}
        </div>
      </InputView>
    </>
  );
};

stories 작성

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

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

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

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