Skip to content

상태는 대체 어디에…

Znero edited this page Dec 1, 2024 · 1 revision

배경

컴포넌트 구조

  • shell 컴포넌트 : 쿼리를 입력할 수 있는 input 태그가 포함되어 있는 컴포넌트
  • shellList 컴포넌트 : shell 컴포넌트를 자식으로 두고 있는 컴포넌트


고민 내용

쉘 컴포넌트를 클릭했을 때 해당 쉘이 포커스 되었는지 알기 위해서는 해당 쉘의 포커스 여부(isFocused)를 저장하는 상태가 필요했습니다. 이때 이 포커스 여부(isFocused) 상태를 부모와 자식 중 어디에 두어야 할지 고민이 컸습니다.




Lifting State Up

처음에는 isFocused 상태를 부모 컴포넌트인 shellList에 두었습니다. 한 번에 한 쉘에만 포커스를 두고 싶었기 때문입니다. 이렇게 하기 위해서는 부모에 isFocused 상태를 두어야 한다고 생각했습니다.

그래서 현재 포커스 중인 쉘 ID를 저장하는 상태 focusedShell을 부모에게 두고, focusedShellsetFocusedShell을 자식에게 props로 넘겼습니다.이렇게 하니 부모의 상태가 바뀔 때마다 자식 쉘들이 다시 리렌더링하면서 깜빡이는 현상이 나타났습니다. (점멸하는 쉘)


// ShellList 컴포넌트
export default function ShellList({ shells }: { shells: ShellType[] }) {
  const [focusedShell, setFocusedShell] = useState<number | null>(null)
  // 현재 포커스 중인 쉘 ID를 상태로 저장

  return (
    <div className="flex flex-1 flex-col gap-3 p-4">
      {shells.map((shell) => (
        <Shell
          key={uuidv4()}
          shell={shell}
          removeShell={removeShell}
          updateShell={updateShell}
          focusedShell={focusedShell}
          setFocusedShell={setFocusedShell}
        />
      ))}
    </div>
  )
}
// Shell 컴포넌트
return (
  <>
    <div className="flex h-12 w-full items-center overflow-hidden rounded-sm bg-secondary">
      <button type="button" className="mr-3 h-full w-12 bg-primary p-3">
        <img src={PlayCircle} alt="play button" />
      </button>
      <input
        type="text"
        defaultValue={inputValue}
        onChange={(e) => setInputValue(e.target.value)}
        // 쉘이 포커스될 때 부모 상태 변경
        onFocus={() => setFocusedShell(shellId)} 
        onBlur={handleBlur}
        className="h-8 w-full border-none bg-secondary p-2 text-base font-medium text-foreground focus:outline-none"
      />
    </div>
  </>
)



Props Drilling

결국 부모가 갖고 있던 IsFocuse를 자식인 shell 컴포넌트에 부여함으로서 리랜더링 문제를 해결했습니다. 쉘 컴포넌트에서 onblur 이벤트가 발생했을때 IsFocuse를 false로 바꾸어 포커스를 해제시켰습니다.

const [focused, setFocused] = useState(false)

const handleBlur = (e: React.FocusEvent<HTMLTextAreaElement>) => {
    if (e.relatedTarget?.id === 'remove-shell-btn') return
    setFocused(false) //포커스 해제
    if (inputValue === prevQueryRef.current || shell.id === null) return
    updateShell({ id: shell.id, query: inputValue })
    prevQueryRef.current = inputValue
  }

Clone this wiki locally