Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Next.js Image in Avatar slots API without TypeScript errors #45443

Open
Demianeen opened this issue Feb 28, 2025 · 4 comments · May be fixed by #45483
Open

Support Next.js Image in Avatar slots API without TypeScript errors #45443

Demianeen opened this issue Feb 28, 2025 · 4 comments · May be fixed by #45483
Labels
bug 🐛 Something doesn't work component: avatar This is the name of the generic UI component, not the React module! customization: dom Component's DOM customizability, e.g. slot typescript

Comments

@Demianeen
Copy link

Demianeen commented Feb 28, 2025

Summary

Problem
MUI’s slots API for Avatar allows replacing the default element, for example:

import Image from 'some-other-image'

<Avatar slots={{ img: Image }} />

However, Next.js <Image> is not a drop-in replacement for <img>. It requires additional props (alt, width, height, etc.) and has a different src type (string | StaticImport), leading to a TypeScript mismatch when used in the slots.img prop.

For example, this code works at runtime, but throws TypeScript error that img and Image types not match:

<Avatar
	src={company.logoUrl ? company.logoUrl : undefined}
	slots={{
		img: Image,
	}}
	slotProps={{
		img: {
			width: 164,
			height: 164,
		},
	}}
	sx={{ width: 164, height: 164 }}
/>

We can't do the type augmentation either, because then we get an typescript error that Subsequent property declarations must have the same type.

Workaround

As a temporary workaround I created AppAvatar that handles the mui Avatar and Next Image integration and added mui Avatar to restricted-imports:

// AppAvatar.tsx
import type { AvatarProps } from '@mui/material'
// eslint-disable-next-line no-restricted-imports
import { Avatar } from '@mui/material'
import Image from 'next/image'

export interface AppAvatarProps {
	src?: string
	alt: string
	width?: number
	height?: number
}

export function AppAvatar<
	RootComponent extends React.ElementType = 'div',
	AdditionalProps = object,
>({
	src,
	alt,
	width = 40,
	height = 40,
	sx,
	...props
}: AppAvatarProps & AvatarProps<RootComponent, AdditionalProps>) {
	return (
		<Avatar
			sx={{
				width: width,
				height: height,
				...sx,
			}}
			{...props}
		>
			{src && <Image src={src} width={width} height={height} alt={alt} />}
		</Avatar>
	)
}
// eslint.config.mjs
'no-restricted-imports': [
	2,
	...
	{
		name: '@mui/material',
		importNames: ['Avatar'],
		message: 'Please use `@/shared/ui/AppAvatar` instead.',
	},
	{
		name: '@mui/material/Avatar',
		message: 'Please use `@/shared/ui/AppAvatar` instead.',
	},
]

It would be good improve mui’s slots API to better support different usecases like Next.js <Image> to improve next.js integration

Related:
#45404

Examples

No response

Motivation

Integrating Next.js <Image> with MUI's <Avatar> is a common use case for Next.js projects that use mui. However, the current slots API does not support it natively, requiring workarounds.

Plus generally different solutions may have a slightly different apis, and while they work with mui at runtime, they still produce type errors. It would be really beneficial to have the ability to expand the slot props with custom overrides

Search keywords: Next Image NextImage

@Demianeen Demianeen added the status: waiting for maintainer These issues haven't been looked at yet by a maintainer label Feb 28, 2025
@siriwatknp siriwatknp added component: avatar This is the name of the generic UI component, not the React module! typescript enhancement This is not a bug, nor a new feature customization: dom Component's DOM customizability, e.g. slot and removed status: waiting for maintainer These issues haven't been looked at yet by a maintainer labels Mar 3, 2025
@siriwatknp
Copy link
Member

Thanks for the feedback, I'll try to find a proper solution for this.

@siriwatknp siriwatknp linked a pull request Mar 5, 2025 that will close this issue
1 task
@siriwatknp
Copy link
Member

siriwatknp commented Mar 5, 2025

I open a fix for the slots.img type but there is another problem which is the slotProps.img types after customized the slots.img.

What happens at the moment is that the slotProps.img does not follow the custom slotProps.img (it's fixed with the default img slot props).

So when user customize the slots.img in this case using Next.js Image (has a different props than the default img slot), the slotProps.img will throw an error.

<Avatar
  slots={{ img: Image }}
  slotProps={{
    img: {
      blurDataURL: '', // << TS error, `blurDataURL` does not exist
    },
  }}
/>

So far, the simplest workaround I found is to use satisfies together with type casting:

<Avatar
  slots={{ img: Image }}
  slotProps={{
    img: {
      blurDataURL: '',
    } satisfies ImageProps,
  } as AvatarProps['slotProps']} // << this is required
/>

The ideal solution would be that slotProps.* is automatically updated based on the slots.*, but I think it will be really complex given that all component is already wrapped by OverridableComponent generic.

@siriwatknp siriwatknp added bug 🐛 Something doesn't work and removed enhancement This is not a bug, nor a new feature labels Mar 5, 2025
@mnajdova
Copy link
Member

mnajdova commented Mar 5, 2025

The ideal solution would be that slotProps.* is automatically updated based on the slots.*, but I think it will be really complex given that all component is already wrapped by OverridableComponent generic.

+1 I am afraid this will just freeze the TS compiler, especially if we use it on all components for all slots. The other option would be loosening a bit the slot props type, or accepting a generic (we'll need to see if this is better in terms of type check, but I think it should be.

@siriwatknp
Copy link
Member

siriwatknp commented Mar 10, 2025

There are 2 issues, the types of slots and slotProps.

The slots issue can be easily fixed by changing the types to React.ElementType (allow any React component).


For slotProps, what happens at the moment is that the slotProps.img does not follow the custom slotProps.img (it's fixed with the default img slot props).

So when user customize the slots.img in this case using Next.js Image (has a different props than the default img slot), the slotProps.img will throw an error.

<Avatar
  slots={{ img: Image }}
  slotProps={{
    img: {
      blurDataURL: '', // << TS error, `blurDataURL` does not exist on the default slotProps.img
    },
  }}
/>

// `blurDataURL` is a specific prop for the Image (Next.js Image) component

Options

There are three aspect to consider, "effort", "performance" and "strictness":

1. Dynamically inferring types

This is the ideal solution, the slotProps.* should change automatically when slots.* is customized.

To achieve this, it's required to update the component type level to sync between slots.* and slotProps.*.
We need to be careful do this as it directly correspond to the TS performance (the existing typings already slow for medium-large size project)

Effort: high
Performance impact: high (affect all components)
strictness: maintained type safety

2. Provide workaround doc to users

Using satisfies is a workaround. Users still get type safety for custom slots and default slots

<Avatar
  slots={{ img: Image }}
  slotProps={{
    img: {
      blurDataURL: '',
    } satisfies ImageProps,
  } as AvatarProps['slotProps']} // << this is required
/>

// `blurDataURL` is a specific prop for the Image (Next.js Image) component

Effort: low (just update the docs)
Performance impact: low (affected only when used)
strictness: maintained type safety

3. loosening the types for the slot props with some defaults

User can use the workaround (2) without casting the type at the end. However, this will affect the users that does not customize the slots because the slotProps will no longer throw error for random props/values (slotProps is no longer strict).

I'd avoid this option at all cost as it does not fix the root cause. A strictness issue will be created by users who does not customize the slots.

Effort: medium (require changes to all components with writing specs)
Performance impact: probably high (it could be faster due to loosening the type, not sure)
strictness: no longer strict, errors are not reported.

Proposal

Do (2) immediately, then create a POC for (1).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug 🐛 Something doesn't work component: avatar This is the name of the generic UI component, not the React module! customization: dom Component's DOM customizability, e.g. slot typescript
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants