[React] 여러 개의 하위 컴포넌트의 state 를 상위 컴포넌트에서 handling 하기 - with `useContext`

corgiLoaf·2023년 12월 18일
0

문제 설명

MainPage 에 위치한 Button을 이용하여 하위 컴포넌트인 FeatureItem 의 상태를 핸들링 해야했다.
컴포넌트 구조는 대략 다음과 같다.

MainPage
- DetailPanel
	- FeaturePanel
    	- FeatureItem
        - FeatureItem
        - FeatureItem
        - ...
- Button

더 자세하게는, FeatureItem 컴포넌트 내부의 style 변수를 상위 계층의 버튼을 통해 제어를 해야하는 상황이었다.

접근

검색을 통해 찾은 접근 방법은 함수를 통해 하위 컴포넌트가 상위 컴포넌트로 값을 전달하는 방법이었다.

예시

간단한 예시를 작성했다.

Child 컴포넌트에서 input 으로 작성한 값을 Parent 컴포넌트에서 띄우는 예제이다.
이를 위해서는 input 으로 작성된 값을 상위 컴포넌트에서 전달받을 수 있어야 한다.

// Child.jsx
import React, { useState } from "react";

const Child = ({ onSubmit }) => {
  const [text, setText] = useState("");

  const handleChange = (e) => {
    setText(e.target.value);
  };

  const handleSubmit = () => {
    if (onSubmit) {
      onSubmit(text);
    }
  };

  return (
    <div>
      <h3>Child</h3>
      <input value={text} onChange={handleChange} />
      <button onClick={handleSubmit}>submit</button>
    </div>
  );
};

export default Child;
// Parent.jsx
import React, { useState } from "react";
import Child from "./Child";

const Parent = () => {
  const [data, setData] = useState();

  const handlSubmit = (text) => {
    setData(text);
  };

  return (
    <div>
      <h1>Parent</h1>
      <span>data: {data}</span>
      <Child onSubmit={handlSubmit} />
    </div>
  );
};

export default Parent;

상위 컴포넌트에서는 하위 컴포넌트에 prop으로 function 을 넘겨주고, 하위 컴포넌트에서 전달받아야 할 값을 해당 function 의 인자로 넘겨받는 구조이다.

문제 2 : 하위 컴포넌트가 여러 개라면?

그런데 만약 다뤄야할 하위 컴포넌트가 한 두 개가 아니고 여러 개라면? 이렇게 일일히 setData 를 만들어 개별의 하위 컴포넌트마다 상태관리를 해주기란 상당히 지저분해진다.
이럴 때 사용할 수 있는 React Hook 으로 useContext 가 있다.

이전과 거의 비슷하지만 GrandParent - Parent - Child1, Child2, Child3 이렇게 구조를 변경하였다.

  • depth 하나 추가
  • 여러 개의 하위 컴포넌트

정도의 변경점이 있다.

이때 가장 상위 컴포넌트에 해당하는 GrandParent 에서 context 를 만들어준다.

// GrandParent.jsx
import React, { useState, createContext } from "react";
import Parent from "./Parent";

export const DataContext = createContext(); // create Context

const GrandParent = () => {
  const [data, setData] = useState({}); // multiple data - Using Array

  const updateData = (key, value) => {
    setData((prevData) => ({ ...prevData, [key]: value }));
  };

  return (
    <DataContext.Provider value={{ data, updateData }}>
      <h1>GrandParent</h1>
      {Object.entries(data).map(([key, value]) => (
        <p key={key}>
          {key} data: {value}
        </p>
      ))}
      <Parent />
    </DataContext.Provider>
  );
};

export default GrandParent;

DataContext 라는 이름의 context 를 만들었다.
이제 전체 return 파트를 해당 context의 Provider 로 감싼다.
이제 해당 context 를 data 를 변경할 가장 하위 컴포넌트 - Child 에서 사용할 것이다.
Context.Providervalue 로는 data state 와 updateData function 을 설정하여 해당 값들을 context의 consumer 가 될 하위 컴포넌트에서 사용할 수 있도록 했다.

차례대로 Parent 와 Child 컴포넌트를 보자.

// Parent.jsx
import React from "react";
import Child from "./Child";

const Parent = () => {

  return (
    <div style={{ border: "1px solid black", padding: "20px" }}>
      <h2>Parent</h2>
      <Child title={1} />
      <Child title={2} />
      <Child title={3} />
    </div>
  );
};

export default Parent;

Parent 컴포넌트는 이제 부가적인 역할을 하지 않고 그냥 상위컴포넌트와 하위컴포넌트를 연결해주는 역할만 수행한다. 값들이 함수 대신 context 를 이용하여 전달될 것이기 때문이다.


// Child.jsx
import React, { useState, useContext } from "react";
import { DataContext } from "./GrandParent";

const Child = ({ title }) => {
  const { updateData } = useContext(DataContext);
  const [text, setText] = useState("");

  const handleChange = (e) => {
    setText(e.target.value);
  };

  const handleSubmit = () => {
    updateData(title, text);
  };

  return (
    <div>
      <h3>Child {title}</h3>
      <input value={text} onChange={handleChange} />
      <button onClick={handleSubmit}>submit</button>
    </div>
  );
};

export default Child;

useContext 공식 문서를 참고하면, context 의 현재 값은 컴포넌트 트리 안에서 이 hook 을 호출하는 컴포넌트의 가장 가까이 있는 provider 의 value prop에 의해 결정된다고 한다.

child 컴포넌트에서 DataContext를 불러와 const { updateData } = useContext(DataContext); 라는 객체를 생성하였다.
이제 버튼을 클릭할때마다 updateData 함수를 호출할 것이다.
이 함수는 args 로 전달받은 값을 data state 에 저장하는 함수이므로, 상위 컴포넌트에서 이제 해당 state 를 이용하여 하위 컴포넌트에서 전달받은 값을 사용할 수 있게 되었다.

profile
삽질하고 기록하기

0개의 댓글