perf: optimize marketplace card re-renders with memoization (#29263)
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
import React from 'react'
|
||||||
import { RiInstallLine } from '@remixicon/react'
|
import { RiInstallLine } from '@remixicon/react'
|
||||||
import { formatNumber } from '@/utils/format'
|
import { formatNumber } from '@/utils/format'
|
||||||
|
|
||||||
@@ -5,7 +6,7 @@ type Props = {
|
|||||||
downloadCount: number
|
downloadCount: number
|
||||||
}
|
}
|
||||||
|
|
||||||
const DownloadCount = ({
|
const DownloadCountComponent = ({
|
||||||
downloadCount,
|
downloadCount,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
return (
|
return (
|
||||||
@@ -16,4 +17,7 @@ const DownloadCount = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Memoize to prevent unnecessary re-renders
|
||||||
|
const DownloadCount = React.memo(DownloadCountComponent)
|
||||||
|
|
||||||
export default DownloadCount
|
export default DownloadCount
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import React from 'react'
|
||||||
import DownloadCount from './base/download-count'
|
import DownloadCount from './base/download-count'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -5,7 +6,7 @@ type Props = {
|
|||||||
tags: string[]
|
tags: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const CardMoreInfo = ({
|
const CardMoreInfoComponent = ({
|
||||||
downloadCount,
|
downloadCount,
|
||||||
tags,
|
tags,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
@@ -33,4 +34,7 @@ const CardMoreInfo = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Memoize to prevent unnecessary re-renders when tags array hasn't changed
|
||||||
|
const CardMoreInfo = React.memo(CardMoreInfoComponent)
|
||||||
|
|
||||||
export default CardMoreInfo
|
export default CardMoreInfo
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
import React, { useMemo } from 'react'
|
||||||
import { useTheme } from 'next-themes'
|
import { useTheme } from 'next-themes'
|
||||||
import { RiArrowRightUpLine } from '@remixicon/react'
|
import { RiArrowRightUpLine } from '@remixicon/react'
|
||||||
import { getPluginDetailLinkInMarketplace, getPluginLinkInMarketplace } from '../utils'
|
import { getPluginDetailLinkInMarketplace, getPluginLinkInMarketplace } from '../utils'
|
||||||
@@ -17,7 +18,7 @@ type CardWrapperProps = {
|
|||||||
showInstallButton?: boolean
|
showInstallButton?: boolean
|
||||||
locale?: string
|
locale?: string
|
||||||
}
|
}
|
||||||
const CardWrapper = ({
|
const CardWrapperComponent = ({
|
||||||
plugin,
|
plugin,
|
||||||
showInstallButton,
|
showInstallButton,
|
||||||
locale,
|
locale,
|
||||||
@@ -31,6 +32,18 @@ const CardWrapper = ({
|
|||||||
const { locale: localeFromLocale } = useI18N()
|
const { locale: localeFromLocale } = useI18N()
|
||||||
const { getTagLabel } = useTags(t)
|
const { getTagLabel } = useTags(t)
|
||||||
|
|
||||||
|
// Memoize marketplace link params to prevent unnecessary re-renders
|
||||||
|
const marketplaceLinkParams = useMemo(() => ({
|
||||||
|
language: localeFromLocale,
|
||||||
|
theme,
|
||||||
|
}), [localeFromLocale, theme])
|
||||||
|
|
||||||
|
// Memoize tag labels to prevent recreating array on every render
|
||||||
|
const tagLabels = useMemo(() =>
|
||||||
|
plugin.tags.map(tag => getTagLabel(tag.name)),
|
||||||
|
[plugin.tags, getTagLabel],
|
||||||
|
)
|
||||||
|
|
||||||
if (showInstallButton) {
|
if (showInstallButton) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -43,12 +56,12 @@ const CardWrapper = ({
|
|||||||
footer={
|
footer={
|
||||||
<CardMoreInfo
|
<CardMoreInfo
|
||||||
downloadCount={plugin.install_count}
|
downloadCount={plugin.install_count}
|
||||||
tags={plugin.tags.map(tag => getTagLabel(tag.name))}
|
tags={tagLabels}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
{
|
{
|
||||||
<div className='absolute bottom-0 hidden w-full items-center space-x-2 rounded-b-xl bg-gradient-to-tr from-components-panel-on-panel-item-bg to-background-gradient-mask-transparent px-4 pb-4 pt-8 group-hover:flex'>
|
<div className='absolute bottom-0 hidden w-full items-center space-x-2 rounded-b-xl bg-gradient-to-tr from-components-panel-on-panel-item-bg to-background-gradient-mask-transparent px-4 pb-4 pt-4 group-hover:flex'>
|
||||||
<Button
|
<Button
|
||||||
variant='primary'
|
variant='primary'
|
||||||
className='w-[calc(50%-4px)]'
|
className='w-[calc(50%-4px)]'
|
||||||
@@ -56,7 +69,7 @@ const CardWrapper = ({
|
|||||||
>
|
>
|
||||||
{t('plugin.detailPanel.operation.install')}
|
{t('plugin.detailPanel.operation.install')}
|
||||||
</Button>
|
</Button>
|
||||||
<a href={getPluginLinkInMarketplace(plugin, { language: localeFromLocale, theme })} target='_blank' className='block w-[calc(50%-4px)] flex-1 shrink-0'>
|
<a href={getPluginLinkInMarketplace(plugin, marketplaceLinkParams)} target='_blank' className='block w-[calc(50%-4px)] flex-1 shrink-0'>
|
||||||
<Button
|
<Button
|
||||||
className='w-full gap-0.5'
|
className='w-full gap-0.5'
|
||||||
>
|
>
|
||||||
@@ -92,7 +105,7 @@ const CardWrapper = ({
|
|||||||
footer={
|
footer={
|
||||||
<CardMoreInfo
|
<CardMoreInfo
|
||||||
downloadCount={plugin.install_count}
|
downloadCount={plugin.install_count}
|
||||||
tags={plugin.tags.map(tag => getTagLabel(tag.name))}
|
tags={tagLabels}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -100,4 +113,7 @@ const CardWrapper = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Memoize the component to prevent unnecessary re-renders when props haven't changed
|
||||||
|
const CardWrapper = React.memo(CardWrapperComponent)
|
||||||
|
|
||||||
export default CardWrapper
|
export default CardWrapper
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import React from 'react'
|
import React, { useMemo } from 'react'
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import { useTheme } from 'next-themes'
|
import { useTheme } from 'next-themes'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
@@ -23,7 +23,7 @@ type Props = {
|
|||||||
payload: Plugin
|
payload: Plugin
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProviderCard: FC<Props> = ({
|
const ProviderCardComponent: FC<Props> = ({
|
||||||
className,
|
className,
|
||||||
payload,
|
payload,
|
||||||
}) => {
|
}) => {
|
||||||
@@ -37,6 +37,9 @@ const ProviderCard: FC<Props> = ({
|
|||||||
const { org, label } = payload
|
const { org, label } = payload
|
||||||
const { locale } = useI18N()
|
const { locale } = useI18N()
|
||||||
|
|
||||||
|
// Memoize the marketplace link params to prevent unnecessary re-renders
|
||||||
|
const marketplaceLinkParams = useMemo(() => ({ language: locale, theme }), [locale, theme])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn('group relative rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg p-4 pb-3 shadow-xs hover:bg-components-panel-on-panel-item-bg', className)}>
|
<div className={cn('group relative rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg p-4 pb-3 shadow-xs hover:bg-components-panel-on-panel-item-bg', className)}>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
@@ -63,7 +66,7 @@ const ProviderCard: FC<Props> = ({
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className='absolute bottom-0 left-0 right-0 hidden items-center gap-2 rounded-xl bg-gradient-to-tr from-components-panel-on-panel-item-bg to-background-gradient-mask-transparent p-4 pt-8 group-hover:flex'
|
className='absolute bottom-0 left-0 right-0 hidden items-center gap-2 rounded-xl bg-gradient-to-tr from-components-panel-on-panel-item-bg to-background-gradient-mask-transparent p-4 pt-4 group-hover:flex'
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
className='grow'
|
className='grow'
|
||||||
@@ -76,7 +79,7 @@ const ProviderCard: FC<Props> = ({
|
|||||||
className='grow'
|
className='grow'
|
||||||
variant='secondary'
|
variant='secondary'
|
||||||
>
|
>
|
||||||
<a href={getPluginLinkInMarketplace(payload, { language: locale, theme })} target='_blank' className='flex items-center gap-0.5'>
|
<a href={getPluginLinkInMarketplace(payload, marketplaceLinkParams)} target='_blank' className='flex items-center gap-0.5'>
|
||||||
{t('plugin.detailPanel.operation.detail')}
|
{t('plugin.detailPanel.operation.detail')}
|
||||||
<RiArrowRightUpLine className='h-4 w-4' />
|
<RiArrowRightUpLine className='h-4 w-4' />
|
||||||
</a>
|
</a>
|
||||||
@@ -96,4 +99,7 @@ const ProviderCard: FC<Props> = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Memoize the component to prevent unnecessary re-renders when props haven't changed
|
||||||
|
const ProviderCard = React.memo(ProviderCardComponent)
|
||||||
|
|
||||||
export default ProviderCard
|
export default ProviderCard
|
||||||
|
|||||||
Reference in New Issue
Block a user