material-ui
material-ui copied to clipboard
react 18.3.0 "A props object containing a "key" prop is being spread into JSX"
Steps to reproduce 🕹
Link to live example: https://mui.com/material-ui/react-autocomplete/#multiple-values
using Next.js
It works, but I get warnings on the console.
Warning: A props object containing a "key" prop is being spread into JSX:
let props = {key: someKey, label: ..., size: ..., className: ..., disabled: ..., data-tag-index: ..., tabIndex: ..., onDelete: ...};
<ForwardRef(Chip) {...props} />
React keys must be passed directly to JSX without using spread:
let props = {label: ..., size: ..., className: ..., disabled: ..., data-tag-index: ..., tabIndex: ..., onDelete: ...};
...
Current behavior 😯
Some of the issues have been identified and addressed at https://github.com/mui/material-ui/issues/39474, and they have already been resolved. However, a similar problem occurs with renderTags when using multiple.
Expected behavior 🤔
It appears that the following code also needs to be modified: https://github.com/mui/material-ui/blob/v5.14.17/packages/mui-material/src/Autocomplete/Autocomplete.js#L506-L525
Also, the type of the props for renderOption is currently defined as React.HTMLAttributes<HTMLLIElement>, but in reality, it includes the key property. This might cause some confusion in TypeScript.
Context 🔦
No response
Your environment 🌎
npx @mui/envinfo
System:
OS: macOS 14.1
Binaries:
Node: 20.9.0 - ~/.volta/tools/image/node/20.9.0/bin/node
Yarn: 1.22.19 - ~/.volta/tools/image/yarn/1.22.19/bin/yarn
npm: 10.2.3 - ~/.volta/tools/image/npm/10.2.3/bin/npm
Browsers:
Chrome: 119.0.6045.123
Edge: Not Found
Safari: 17.1
npmPackages:
@emotion/react: ^11.11.1 => 11.11.1
@emotion/styled: ^11.11.0 => 11.11.0
@mui/base: 5.0.0-beta.23
@mui/core-downloads-tracker: 5.14.17
@mui/icons-material: ^5.14.16 => 5.14.16
@mui/lab: ^5.0.0-alpha.152 => 5.0.0-alpha.152
@mui/material: ^5.14.17 => 5.14.17
@mui/private-theming: 5.14.17
@mui/styled-engine: 5.14.17
@mui/system: 5.14.17
@mui/types: 7.2.8
@mui/utils: 5.14.17
@mui/x-date-pickers: ^6.18.1 => 6.18.1
@types/react: ^18.2.37 => 18.2.37
react: ^18.2.0 => 18.2.0
react-dom: 18.2.0 => 18.2.0
typescript: ^5.2.2 => 5.2.2
If it's a Next.js regression, maybe it's for them to fix it, but maybe not depending on what they say about it. If they say they will fix it in 4 weeks, I don't think it's worth it for us, if it's "normal", 👍 to handle.
At least it's a tradeoff, we are adding complexity to the codebase that is a good benefit to 1/4 of the users (Next.js userbase?) and harms a little bit 3/4.
Thank you for your quick reply! And, thank you for commenting on the Next.js issue. It indeed seems to be an issue with Next.js, and I agree that it should be addressed on the Next.js side rather than MUI's.
This is not only in Next.js. It also happens with React starting from version 18.3.1
when not using Next.js
True https://github.com/facebook/react/releases/tag/v18.3.0
I had a closer look at this. The behavior of the JSX transform is interesting:
const profile1 = (
<div key="baarr" {...foo} />
);
const profile2 = (
<div {...foo} key="baarr" />
);
outputs:
import { jsx as _jsx } from "react/jsx-runtime";
import { createElement as _createElement } from "react";
const profile1 = /*#__PURE__*/_jsx("div", {
...foo
}, "baarr");
const profile2 = /*#__PURE__*/_createElement("div", {
...foo,
key: "baarr"
});
Now, when you check the sources, I'm super confused: https://github.com/facebook/react/blob/04b058868c9fc61c78124b12efb168734d79d09e/packages/react/src/jsx/ReactJSXElement.js#L557-L562. But it gets a lot clearer from: https://github.com/reactjs/rfcs/blob/createlement-rfc/text/0000-create-element-changes.md
The goal is to bring element creation down to this logic:
function jsx(type, props, key) { return { $$typeof: ReactElementSymbol, type, key, props, }; }
So what React wants is a clear key
prop, so the Babel plugin can pass it to the right argument. This key needs to be applied first, React then relies on TypeScript to flag duplicate keys when the spread has a key
property. Smart
I have created #42168 to illustrate it.
Thanks for taking a deeper look @oliviertassinari!
Summarizing, if I understand correctly
1. key
must go first so the jsx
function is used.
The reason <div {...foo} key="baarr" />
outputs the following:
_createElement("div", { ...foo, key: "baarr" });
instead of using jsx
as follows:
// jsx signature: (type, config, maybeKey)
_jsx("div", { ...foo }, "baarr")
is because in jsx
, config.key
(foo.key
) would override maybeKey
("baarr"
) (reference), which is not expected when <div {...foo} key="baarr" />
.
It seems like this issue could go away when they stop pulling foo.key
🤔, doesn't it?
2. key
must be removed from props to be ready for future changes and remove the warning.
This is because even though the jsx
function pulls it it at the moment (reference), it won't do it forever (reference). This is where the warning is coming from (reference). This warning will not be removed even when they stop pulling key
from it (reference).
Please correct me if this summary is incorrect.
@DiegoAndai This matches what I understand of the situation 👍
Hi, I've been having this issue using Next.js 14 + React 18 + MUI 5.15
This error comes up when you select an item and the Chip appears within the input, not sure if you already solve it but I was able to solve it overriding the Chip with the renderTags:
<Autocomplete
multiple
...
renderTags={(value, getTagProps) =>
value.map((option, index) => {
const { key, ...tagProps } = getTagProps({ index });
return <Chip variant={'outlined'} color={'primary'} size={"small"} key={key} label={option.title} {...tagProps} />;
})
}
/>
Basically you cannot pass the {...props} containing a key
prop so you need to destructure key from props
For example:
renderOption={(props, option, { selected }) => {
const { key, ...rest } = props;
return (
<li key={key} {...rest}>
...
</li>
);
}}
Hope it works to other folks!
I raised an issue https://github.com/mui/material-ui/issues/42161 which got closed as a duplicate of this one. However, the fixes that went in to v5.15.19 don't seem to address it so i'm hoping it will still be tracked and resolved.
Whilst as @amd2107 points out, there is a key
prop passed within the props object to the renderOptions
, the typescript has no mention of it it seems, meaning you have to cast the props yourself.
@DiegoAndai are there plans to address this still?
I have the same issue - React + mui, mostly with the Autocomplete I had to do unnecessary renderTags to solve it:
renderTags={(value, getTagProps) => value.map((option, index) => {
const tagProps = getTagProps({ index });
return (
<Chip
label={option.name}
{...tagProps}
key={option.id}
/>
);
})}
I raised an issue https://github.com/mui/material-ui/issues/42161 which got closed as a duplicate of this one. However, the fixes that went in to v5.15.19 don't seem to address it so i'm hoping it will still be tracked and resolved.
[...]
@DiegoAndai are there plans to address this still?
Yes, I'll take a look as soon as possible, sorry for the delay. I'll keep you posted.
@mbiggs-gresham @a-tonchev you should upgrade to the following version:
npm i @mui/[email protected] --save or npm i @mui/material@next --save
I spent several hours exploring how MUI works behind the scenes. While testing in the integrated playground, I upgraded to Next.js 14, and the error was resolved. I believe the issue has been fixed in this latest version.
Hope it works!
@oliviertassinari @DiegoAndai
This topic can be closed as the error has been resolved in the mentioned version.
Edit: I removed the part where I mentioned compatibility with version 7 and other MUI plugins. Initially, it seemed to work, but after rebuilding the project and fixing the component styles, it resulted in a failure. Note that there are no compatible grid or time picker libraries yet for this next version.
I was also seeing this issue in react and I am not using Nextjs.. I was also able to resolve the issue by de-structing the object that is being passed into the renderOption with muiv5. Basically, the key prop must be removed before passing into the <li>
elements. For me, it was like -
renderOption={(props, option) => {
const { key, ...rest } = props;
return (
<li key={key} {...rest}>
{option.location}
</li>
);
}}
The answer above from @amd2107 and @a-tonchev helped me. Hope it helps others as well.
An update. This issue covers two related bugs:
The first bug; key spreading inside the Autocomplete component codebase, was fixed in https://github.com/mui/material-ui/pull/42099 and is available from versions v5.15.19
and v6.0.0-alpha.7
onwards.
To close this issue, we need to fix the second one: renderOptions
prop's props
argument, which is incorrectly typed as React.HTMLAttributes<HTMLLIElement>
missing the key
property, forcing people to cast it as reported in this issue as well as in https://github.com/mui/material-ui/issues/42161. Sorry for the delay @mbiggs-gresham.
Other components might have similar issues to the one I mentioned above. We should look for and fix those in the same PR. This is included in the support React 19 work, so it should be picked up in the coming weeks.
Thank for your patience.
Thanks for the update
The renderOption
type issue has been fixed in https://github.com/mui/material-ui/pull/42689 and cherry-picked in https://github.com/mui/material-ui/pull/42709. The fix will be out on next week's release.
Please let us know if you encounter the same issue again or a similar one.
Hi, I'm encountering a similar issue on Joy Autocomplete (v5.0.0-beta.36). I'm willing to make a pull request, if you can point me to the right direction.
I am on react 18 btw.
console.js:288 Warning: A props object containing a "key" prop is being spread into JSX:
let props = {key: someKey, as: ..., className: ..., ref: ..., ownerState: ..., tabIndex: ..., role: ..., id: ..., onMouseMove: ..., onClick: ..., onTouchStart: ..., data-option-index: ..., aria-disabled: ..., aria-selected: ..., children: ...};
<JoyAutocompleteOption {...props} />
React keys must be passed directly to JSX without using spread:
let props = {as: ..., className: ..., ref: ..., ownerState: ..., tabIndex: ..., role: ..., id: ..., onMouseMove: ..., onClick: ..., onTouchStart: ..., data-option-index: ..., aria-disabled: ..., aria-selected: ..., children: ...};
<JoyAutocompleteOption key={someKey} {...props} />
<FormControl>
<FormLabel>...</FormLabel>
<Autocomplete
multiple
freeSolo
placeholder="..."
options={data ?? []}
getOptionLabel={(option) => option}
/>
</FormControl>
Hey @keyvanm, have you tried with the latest Joy version: 5.0.0-beta.47
?
Hey @keyvanm, have you tried with the latest Joy version:
5.0.0-beta.47
?
Just upgraded. Same issue.
We have a change that might fix this in this week's release: 5.0.0-beta.48
(not released yet but should come soon). Please wait until then to test again. If that doesn't fix it, may I ask you to provide a minimal reproduction? This would help a lot. A live example would be perfect. This StackBlitz sandbox template may be a good starting point. Thank you!