Repository: streamlit/streamlit
File: frontend/lib/src/components/widgets/Multiselect/Multiselect.tsx (TypeScript)

Code before:
      return createFilterOptions(value)(options, filterValue)
    },
    [createFilterOptions, overMaxSelections, value]
  )

  const disabled = props.disabled || shouldDisable
  const valueFromState = useMemo(
    () => valuesToUiMulti(value),
    [valuesToUiMulti, value]
  )

  // Calculate the max height of the selectbox based on the baseFontSize
  // to better support advanced theming
  const maxHeight = useMemo(() => {
    // Option height = lineHeight (1.6 * baseFontSize) + margin/padding (14px total)
    const optionHeight = theme.fontSizes.baseFontSize * 1.6 + 14
    // Allow up to 4 options tall before scrolling + show small portion
    // of the next row so its clear the user can scroll
    const pxMaxHeight = Math.round(optionHeight * 4.25)
    // Return value in px
    return `${pxMaxHeight}px`
  }, [theme.fontSizes.baseFontSize])

  useLayoutEffect(() => {
    if (valueContainerRef.current) {
      valueContainerRef.current.scrollTop = scrollTopRef.current
    }
  })

  const handleValueContainerScroll = useCallback(
    (e: React.UIEvent<HTMLDivElement>) => {
      // eslint-disable-next-line streamlit-custom/no-force-reflow-access -- Safe: layout already computed during scroll event
      scrollTopRef.current = e.currentTarget.scrollTop
    },
    []
  )

  // Memoized to prevent BaseWeb from remounting on every render
  const ValueContainer = useMemo(
    () =>
      // eslint-disable-next-line @eslint-react/no-nested-component-definitions -- Required for baseweb component override with refs
      function ValueContainer(
        props: SharedStylePropsArg & { children: React.ReactNode }
      ): React.ReactElement {
        return (
          <StyledValueContainer
            {...props}
            ref={valueContainerRef}
            onScroll={handleValueContainerScroll}
          />
        )

Review comment (performance):
@kagawa0710 just one more feedback item, can you add a comment here explaining something like -- we need to use `useLayoutEffect` because updates can also happen in the base web library and will otherwise not be captured in our component. We could also mention that we believe the performance implications are acceptable because this is a leaf node (thanks @sfc-gh-bnisco for also taking a look).

Then I am ready to approve, thank you!

Code after:
      return createFilterOptions(value)(options, filterValue)
    },
    [createFilterOptions, overMaxSelections, value]
  )

  const disabled = props.disabled || shouldDisable
  const valueFromState = useMemo(
    () => valuesToUiMulti(value),
    [valuesToUiMulti, value]
  )

  // Calculate the max height of the selectbox based on the baseFontSize
  // to better support advanced theming
  const maxHeight = useMemo(() => {
    // Option height = lineHeight (1.6 * baseFontSize) + margin/padding (14px total)
    const optionHeight = theme.fontSizes.baseFontSize * 1.6 + 14
    // Allow up to 4 options tall before scrolling + show small portion
    // of the next row so its clear the user can scroll
    const pxMaxHeight = Math.round(optionHeight * 4.25)
    // Return value in px
    return `${pxMaxHeight}px`
  }, [theme.fontSizes.baseFontSize])

  // Runs every render to capture BaseWeb's internal DOM updates that can reset scroll position.
  // Performance is acceptable since this is a leaf component with no children to re-render.
  useLayoutEffect(() => {
    if (valueContainerRef.current) {
      valueContainerRef.current.scrollTop = scrollTopRef.current
    }
  })

  const handleValueContainerScroll = useCallback(
    (e: React.UIEvent<HTMLDivElement>) => {
      // eslint-disable-next-line streamlit-custom/no-force-reflow-access -- Safe: layout already computed during scroll event
      scrollTopRef.current = e.currentTarget.scrollTop
    },
    []
  )

  // Memoized to prevent BaseWeb from remounting on every render
  const ValueContainer = useMemo(
    () =>
      // eslint-disable-next-line @eslint-react/no-nested-component-definitions -- Required for baseweb component override with refs
      function ValueContainer(
        props: SharedStylePropsArg & { children: React.ReactNode }
      ): React.ReactElement {
        return (
          <StyledValueContainer
            {...props}
            ref={valueContainerRef}
            onScroll={handleValueContainerScroll}