From 1fa1309e4fb2310b4253504c0322ae78652c34d9 Mon Sep 17 00:00:00 2001 From: nabi <42037851+nabi-chan@users.noreply.github.com> Date: Wed, 29 May 2024 15:36:57 +0900 Subject: [PATCH] fix console errors from bezier-react (#2238) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Self Checklist - [x] I wrote a PR title in **English** and added an appropriate **label** to the PR. - [x] I wrote the commit message in **English** and to follow [**the Conventional Commits specification**](https://www.conventionalcommits.org/en/v1.0.0/). - [x] I [added the **changeset**](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md) about the changes that needed to be released. (or didn't have to) - [x] I wrote or updated **documentation** related to the changes. (or didn't have to) - [x] I wrote or updated **tests** related to the changes. (or didn't have to) - [x] I tested the changes in various browsers. (or didn't have to) - Windows: Chrome, Edge, (Optional) Firefox - macOS: Chrome, Edge, Safari, (Optional) Firefox ## Related Issue None ## Summary bezier-react 를 사용할 때 뜨는 리액트 경고를 해결합니다. ## Details ### ToastContainer 내 `Each child in a list should have a unique "key" prop.` 경고 ![스크린샷 2024-05-24 22 38 43](https://github.com/channel-io/bezier-react/assets/42037851/86ac65cf-ce9c-4a2d-9e0d-1930efee0ea2) 이 이슈는 다음의 원인으로 인해 발생하는 이슈입니다. `packages/bezier-react/src/components/Toast/Toast.tsx` 내 `createContainer` 함수에서 InvertedThemeProvider를 사용하게 되는데, 이때 해당 컴포넌트에 key값이 들어가지 않아 생기는 에러입니다. 이를 InvertedThemeProvider 내에 넣도록 변경함으로써, 문제를 해결하고자 합니다. ### BaseTagBadgeText 내 `forwardRef render functions accept exactly two parameters: props and ref. Did you forget to use the ref parameter?` 에러 ![스크린샷 2024-05-24 22 42 22](https://github.com/channel-io/bezier-react/assets/42037851/50064080-9ba0-4426-aa75-21931d9bd5ac) 이 이슈는 다음 원인으로 인해 발생됨이 추정됩니다. stack trace를 따라가다보면, 최종 끝에 다다르는 파일이 `BaseTagBadge.mjs` 에 위치하게 되는데, 여기서 유일하게 ref parameter를 사용하지 않는 컴포넌트는 `BaseTagBadgeText` 컴포넌트가 유일합니다. 따라서 해당 컴포넌트에 forwardRef를 사용하게 함으로써 이 문제를 해결하고자 합니다. ### NextJS와 같은 SSR 환경에서 useLayoutEffect가 동작하지 않는 이슈 ``` Warning: useLayoutEffect does nothing on the server, because its effect cannot be encoded into the server renderer's output format. This will lead to a mismatch between the initial, non-hydrated UI and the intended UI. To avoid this, useLayoutEffect should only be used in components that render exclusively on the client. See https://reactjs.org/link/uselayouteffect-ssr for common fixes. ``` https://reactjs.org/link/uselayouteffect-ssr 에 따르면, SSR 환경에서는 useLayoutEffect가 동작하지 않게 되고, 이는 곧 hydration mismatch로 이어지게 되어 해당 경고가 출력됨을 안내하고 있습니다. 여기서 제시하는 방법은 총 2가지로, 1. `useLayoutEffect` 를 `useEffect` 로 변경하거나, 2. useLayoutEffect를 사용하는 컴포넌트를 Lazy하게 로딩하는 방법을 제시하고 있습니다. 하지만 위 2가지 방법은 각각 다음과 같은 문제가 있는데요. - 1번의 경우 : useEffect를 사용하게 되어 처음 의도하였던 UX에서 벗어날 여지가 있습니다. - 2번의 경우 : 컴포넌트를 Lazy하게 로딩하는 경우, SSR 환경에서 하위 컴포넌트가 로딩되지 않아 SEO에 부정적인 영향을 미치게 됩니다. 이 대신 서버사이드 렌더링 환경에서는 useEffect를 반환하고, 클라이언트사이드 환경에서는 useLayoutEffect를 사용하는 `useIsomorphicLayoutEffect` 을 만들어 이 문제를 해결하고자 합니다. ([#](https://medium.com/@alexandereardon/uselayouteffect-and-ssr-192986cdcf7a)) 해당 방식은 [react-redux](https://github.com/reduxjs/react-redux/blob/d16262582b2eeb62c05313fca3eb59dc0b395955/src/components/connectAdvanced.js#L40) 와 [react-beautiful-dnd](https://github.com/atlassian/react-beautiful-dnd/blob/master/src/view/use-isomorphic-layout-effect.js) 와 같이 유명한 라이브러리에서 사용되고 있어, 안정성이 보장된다고 판단됩니다. ### Breaking change? (Yes/No) NO ## References - [useLayoutEffect and SSR](https://medium.com/@alexandereardon/uselayouteffect-and-ssr-192986cdcf7a) --------- Co-authored-by: Yang Wooseong (Andrew) --- .changeset/red-worms-argue.md | 6 ++++++ .../bezier-react/src/components/AutoFocus/AutoFocus.tsx | 5 +++-- .../src/components/BaseTagBadge/BaseTagBadge.tsx | 3 ++- .../src/components/FeatureProvider/FeatureProvider.tsx | 5 +++-- packages/bezier-react/src/components/Overlay/Overlay.tsx | 6 +++--- .../bezier-react/src/components/TextArea/TextArea.tsx | 5 +++-- packages/bezier-react/src/components/Toast/Toast.tsx | 3 +-- .../bezier-react/src/hooks/useIsomorphicLayoutEffect.ts | 9 +++++++++ 8 files changed, 30 insertions(+), 12 deletions(-) create mode 100644 .changeset/red-worms-argue.md create mode 100644 packages/bezier-react/src/hooks/useIsomorphicLayoutEffect.ts diff --git a/.changeset/red-worms-argue.md b/.changeset/red-worms-argue.md new file mode 100644 index 0000000000..254b7415d9 --- /dev/null +++ b/.changeset/red-worms-argue.md @@ -0,0 +1,6 @@ +--- +"@channel.io/bezier-react": patch +--- + +- Fix ReactJS console warnings. +- Introduce `useIsomorphicLayoutEffect` hook to use `useLayoutEffect` in SSR environment. diff --git a/packages/bezier-react/src/components/AutoFocus/AutoFocus.tsx b/packages/bezier-react/src/components/AutoFocus/AutoFocus.tsx index 0e672ca32e..a2f276dd7b 100644 --- a/packages/bezier-react/src/components/AutoFocus/AutoFocus.tsx +++ b/packages/bezier-react/src/components/AutoFocus/AutoFocus.tsx @@ -1,7 +1,8 @@ -import React, { forwardRef, useLayoutEffect, useState } from 'react' +import React, { forwardRef, useState } from 'react' import { Slot } from '@radix-ui/react-slot' +import { useIsomorphicLayoutEffect } from '~/src/hooks/useIsomorphicLayoutEffect' import useMergeRefs from '~/src/hooks/useMergeRefs' import { type AutoFocusProps } from './AutoFocus.types' @@ -27,7 +28,7 @@ export const AutoFocus = forwardRef( function AutoFocus({ children, when = true, ...rest }, forwardedRef) { const [target, setTarget] = useState(null) - useLayoutEffect( + useIsomorphicLayoutEffect( function focus() { if (target && when) { target.focus() diff --git a/packages/bezier-react/src/components/BaseTagBadge/BaseTagBadge.tsx b/packages/bezier-react/src/components/BaseTagBadge/BaseTagBadge.tsx index a6ff838543..b85d096a7e 100644 --- a/packages/bezier-react/src/components/BaseTagBadge/BaseTagBadge.tsx +++ b/packages/bezier-react/src/components/BaseTagBadge/BaseTagBadge.tsx @@ -51,10 +51,11 @@ export const BaseTagBadge = forwardRef( export const BaseTagBadgeText = forwardRef< HTMLDivElement, BaseTagBadgeTextProps ->(function BaseTagBadgeText({ size, children, ...rest }) { +>(function BaseTagBadgeText({ size, children, ...rest }, forwardedRef) { return ( {children} diff --git a/packages/bezier-react/src/components/FeatureProvider/FeatureProvider.tsx b/packages/bezier-react/src/components/FeatureProvider/FeatureProvider.tsx index 0b323245bf..cc6ac0c5e2 100644 --- a/packages/bezier-react/src/components/FeatureProvider/FeatureProvider.tsx +++ b/packages/bezier-react/src/components/FeatureProvider/FeatureProvider.tsx @@ -1,5 +1,6 @@ -import React, { useLayoutEffect, useMemo, useState } from 'react' +import React, { useMemo, useState } from 'react' +import { useIsomorphicLayoutEffect } from '~/src/hooks/useIsomorphicLayoutEffect' import { createContext } from '~/src/utils/react' import { isEmpty } from '~/src/utils/type' @@ -30,7 +31,7 @@ export function FeatureProvider({ children, features }: FeatureProviderProps) { const [featureFlag, setFeatureFlag] = useState(initialFeatureFlag) - useLayoutEffect( + useIsomorphicLayoutEffect( function activateFeatures() { if (isEmpty(features)) { return diff --git a/packages/bezier-react/src/components/Overlay/Overlay.tsx b/packages/bezier-react/src/components/Overlay/Overlay.tsx index 6054183de4..7be4fc30f1 100644 --- a/packages/bezier-react/src/components/Overlay/Overlay.tsx +++ b/packages/bezier-react/src/components/Overlay/Overlay.tsx @@ -2,7 +2,6 @@ import React, { forwardRef, useCallback, useEffect, - useLayoutEffect, useReducer, useRef, useState, @@ -12,6 +11,7 @@ import ReactDOM from 'react-dom' import classNames from 'classnames' import useEventHandler from '~/src/hooks/useEventHandler' +import { useIsomorphicLayoutEffect } from '~/src/hooks/useIsomorphicLayoutEffect' import useMergeRefs from '~/src/hooks/useMergeRefs' import { useModalContainerContext } from '~/src/components/Modal' @@ -94,7 +94,7 @@ export const Overlay = forwardRef( } }, [container, hasContainer]) - useLayoutEffect( + useIsomorphicLayoutEffect( function initContainerRect() { if (show) { handleContainerRect() @@ -125,7 +125,7 @@ export const Overlay = forwardRef( } }, [target]) - useLayoutEffect( + useIsomorphicLayoutEffect( function initTargetRect() { if (show) { handleTargetRect() diff --git a/packages/bezier-react/src/components/TextArea/TextArea.tsx b/packages/bezier-react/src/components/TextArea/TextArea.tsx index 62e16602e0..565522d711 100644 --- a/packages/bezier-react/src/components/TextArea/TextArea.tsx +++ b/packages/bezier-react/src/components/TextArea/TextArea.tsx @@ -1,8 +1,9 @@ -import React, { forwardRef, useLayoutEffect, useRef } from 'react' +import React, { forwardRef, useRef } from 'react' import classNames from 'classnames' import TextareaAutosize from 'react-textarea-autosize' +import { useIsomorphicLayoutEffect } from '~/src/hooks/useIsomorphicLayoutEffect' import { COMMON_IME_CONTROL_KEYS, useKeyboardActionLockerWhileComposing, @@ -43,7 +44,7 @@ export const TextArea = forwardRef( onKeyUp, }) - useLayoutEffect(function initialAutoFocus() { + useIsomorphicLayoutEffect(function initialAutoFocus() { function setSelectionToEnd() { inputRef.current?.setSelectionRange( inputRef.current?.value.length, diff --git a/packages/bezier-react/src/components/Toast/Toast.tsx b/packages/bezier-react/src/components/Toast/Toast.tsx index cb7b86f41c..ee038ac2e1 100644 --- a/packages/bezier-react/src/components/Toast/Toast.tsx +++ b/packages/bezier-react/src/components/Toast/Toast.tsx @@ -205,9 +205,8 @@ export function ToastProvider({ const createContainer = useCallback( (placement: ToastPlacement, toasts: ToastType[]) => ( - +