diff --git a/docs/data/material/migration/upgrade-to-v7/migrating-from-deprecated-apis.md b/docs/data/material/migration/upgrade-to-v7/migrating-from-deprecated-apis.md new file mode 100644 index 00000000000000..4e32fbb8a87a0f --- /dev/null +++ b/docs/data/material/migration/upgrade-to-v7/migrating-from-deprecated-apis.md @@ -0,0 +1,69 @@ +# Migrating from deprecated APIs + +

Learn how to migrate away from recently deprecated APIs before they become breaking changes.

+ +## Why you should migrate + +Features become deprecated over time as maintainers make improvements to the APIs. +Migrating to these improved APIs results in a better developer experience, so it's in your best interest to stay up to date. +Deprecated APIs often become breaking changes in subsequent major versions, so the sooner you migrate, the smoother the next major update will be. + +## Autocomplete + +Use the [codemod](https://github.com/mui/material-ui/tree/HEAD/packages/mui-codemod#autocomplete-props) below to migrate the code as described in the following sections: + +```bash +npx @mui/codemod@latest deprecations/autocomplete-props +``` + +### renderTags prop + +The `renderTags` prop is deprecated, use `renderValue` instead. + +```diff + +- value.map((option, index) => ( +- +- )) +- } ++ renderValue={(value, getItemProps, ownerState) => ++ value.map((option, index) => ( ++ ++ )) ++ } + /> +``` + +--- + +### useAutocomplete deprecated fields + +The following return value fields are deprecated from the `useAutocomplete` hook: + +- `getTagProps` → use `getItemProps` +- `focusedTag` → use `focusedItem` + +#### getTagProps + +```diff + const { +- getTagProps, ++ getItemProps, + } = useAutocomplete(props); + + // ... +- ++ +``` + +#### focusedTag + +```diff + const { +- focusedTag, ++ focusedItem, + } = useAutocomplete(props); +``` diff --git a/docs/data/material/migration/upgrade-to-v7/upgrade-to-v7.md b/docs/data/material/migration/upgrade-to-v7/upgrade-to-v7.md index 4e60553eb7f55f..0a73e3d2f7e5ee 100644 --- a/docs/data/material/migration/upgrade-to-v7/upgrade-to-v7.md +++ b/docs/data/material/migration/upgrade-to-v7/upgrade-to-v7.md @@ -139,6 +139,13 @@ Material UI v7 uses `react-is@19`, which changed how React elements are identif If you're on React 18 or below, mismatched versions of `react-is` can cause runtime errors in prop type checks. Forcing `react-is` to match your React version prevents these errors. +## Deprecations + +It is not required to immediately go through the deprecations in order to use Material UI v7. + +You can do it at your own pace by checking out the [deprecations page](/material-ui/migration/v7/migrating-from-deprecated-apis/). +Those deprecations will be removed in the next major version. + ## Breaking changes Since v7 is a new major release, it contains some changes that affect the public API. diff --git a/docs/data/material/pages.ts b/docs/data/material/pages.ts index 339a0cedf6221d..f7d02ea8a5b6e9 100644 --- a/docs/data/material/pages.ts +++ b/docs/data/material/pages.ts @@ -317,6 +317,10 @@ const pages: MuiPage[] = [ pathname: '/material-ui/migration/upgrade-to-v7', title: 'Upgrade to v7: getting started', }, + { + pathname: '/material-ui/migration/v7/migrating-from-deprecated-apis', + title: 'Migrating from deprecated APIs', + }, { pathname: '/material-ui/migration/upgrade-to-native-color', title: 'Native color', diff --git a/docs/pages/material-ui/migration/v7/migrating-from-deprecated-apis.js b/docs/pages/material-ui/migration/v7/migrating-from-deprecated-apis.js new file mode 100644 index 00000000000000..88f2104b5bd3b8 --- /dev/null +++ b/docs/pages/material-ui/migration/v7/migrating-from-deprecated-apis.js @@ -0,0 +1,6 @@ +import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs'; +import * as pageProps from 'docs/data/material/migration/upgrade-to-v7/migrating-from-deprecated-apis.md?muiMarkdown'; + +export default function Page() { + return ; +} diff --git a/docs/translations/translations.json b/docs/translations/translations.json index f1f7b5f6ea016b..04e35f79d3d733 100644 --- a/docs/translations/translations.json +++ b/docs/translations/translations.json @@ -177,6 +177,7 @@ "/material-ui/migration/pickers-migration": "Migration from @material-ui/pickers", "Upgrade to v7": "Upgrade to v7", "/material-ui/migration/upgrade-to-v7": "Upgrade to v7: getting started", + "/material-ui/migration/v7/migrating-from-deprecated-apis": "Migrating from deprecated APIs", "/material-ui/migration/upgrade-to-native-color": "Native color", "Upgrade to v6": "Upgrade to v6", "/material-ui/migration/upgrade-to-v6": "Upgrade to v6: getting started", diff --git a/packages/mui-codemod/README.md b/packages/mui-codemod/README.md index 0d36aa921a0ea8..26ee2754fffa01 100644 --- a/packages/mui-codemod/README.md +++ b/packages/mui-codemod/README.md @@ -278,6 +278,11 @@ npx @mui/codemod@latest deprecations/alert-props - PopperComponent={CustomPopper} - ListboxComponent={CustomListbox} - ListboxProps={{ height: 12 }} +- renderTags={(value, getTagProps, ownerState) => +- value.map((option, index) => ( +- +- )) +- } - componentsProps={{ - clearIndicator: { width: 10 }, - paper: { width: 12 }, @@ -299,6 +304,11 @@ npx @mui/codemod@latest deprecations/alert-props + popper: { width: 14 }, + popupIndicator: { width: 16 }, + }} ++ renderValue={(value, getItemProps, ownerState) => ++ value.map((option, index) => ( ++ ++ )) ++ } /> ``` @@ -310,6 +320,10 @@ npx @mui/codemod@latest deprecations/alert-props - PopperComponent: CustomPopper, - ListboxComponent: CustomListbox, - ListboxProps: { height: 12 }, +- renderTags: (value, getTagProps, ownerState) => +- value.map((option, index) => ( +- +- )), - componentsProps: { - clearIndicator: { width: 10 }, - paper: { width: 12 }, @@ -331,10 +345,23 @@ npx @mui/codemod@latest deprecations/alert-props + popper: { width: 14 }, + popupIndicator: { width: 16 }, + }, ++ renderValue: (value, getItemProps, ownerState) => ++ value.map((option, index) => ( ++ ++ )), }, }, ``` +```diff + const { +- getTagProps, +- focusedTag, ++ getItemProps, ++ focusedItem, + } = useAutocomplete(props); +``` + ```bash npx @mui/codemod@latest deprecations/autocomplete-props ``` diff --git a/packages/mui-codemod/src/deprecations/autocomplete-props/autocomplete-props.js b/packages/mui-codemod/src/deprecations/autocomplete-props/autocomplete-props.js index 5ec12ceb7c25ee..95fdd84eb5c4e3 100644 --- a/packages/mui-codemod/src/deprecations/autocomplete-props/autocomplete-props.js +++ b/packages/mui-codemod/src/deprecations/autocomplete-props/autocomplete-props.js @@ -6,6 +6,99 @@ import findComponentDefaultProps from '../../util/findComponentDefaultProps'; import assignObject from '../../util/assignObject'; import appendAttribute from '../../util/appendAttribute'; +function isNonComputedKey(j, path) { + const parent = path.parent.node; + + return ( + (j.ObjectProperty.check(parent) || j.Property.check(parent)) && + parent.key === path.node && + !parent.computed + ); +} + +function renameIdentifiersInScope(j, scopePath, oldName, newName) { + const bindingScope = scopePath.scope.lookup(oldName); + + if (!bindingScope) { + return; + } + + j(bindingScope.path) + .find(j.Identifier, { name: oldName }) + .filter((path) => { + if (isNonComputedKey(j, path)) { + return false; + } + + return path.scope.lookup(oldName) === bindingScope; + }) + .replaceWith(() => j.identifier(newName)); +} + +function renameRenderTagsCallback(j, callbackPath) { + const getTagPropsParam = callbackPath.node.params[1]; + + if (getTagPropsParam?.type === 'Identifier' && getTagPropsParam.name === 'getTagProps') { + renameIdentifiersInScope(j, callbackPath, 'getTagProps', 'getItemProps'); + } +} + +function renameRenderTagsProp(j, propertyPath) { + if (propertyPath.node.key.type === 'Identifier') { + propertyPath.node.key.name = 'renderValue'; + } + + if ( + propertyPath.node.value.type === 'ArrowFunctionExpression' || + propertyPath.node.value.type === 'FunctionExpression' + ) { + renameRenderTagsCallback(j, propertyPath.get('value')); + } +} + +function renameUseAutocompleteReturnMembers(j, root) { + const renamedMembers = new Map([ + ['getTagProps', 'getItemProps'], + ['focusedTag', 'focusedItem'], + ]); + + root + .find(j.VariableDeclarator) + .filter((path) => { + const { id, init } = path.node; + + return ( + id.type === 'ObjectPattern' && + init?.type === 'CallExpression' && + init.callee.type === 'Identifier' && + init.callee.name === 'useAutocomplete' + ); + }) + .forEach((path) => { + path.node.id.properties.forEach((property) => { + if (property.type !== 'ObjectProperty' || property.key.type !== 'Identifier') { + return; + } + + const nextName = renamedMembers.get(property.key.name); + + if (!nextName) { + return; + } + + const isShorthand = property.shorthand === true; + const localName = property.value.type === 'Identifier' ? property.value.name : null; + + property.key.name = nextName; + + if (isShorthand && localName) { + renameIdentifiersInScope(j, path, localName, nextName); + property.shorthand = true; + } + }); + }); +} + /** * @param {import('jscodeshift').FileInfo} file * @param {import('jscodeshift').API} api @@ -59,6 +152,26 @@ export default function transformer(file, api, options) { { root, packageName: options.packageName, componentName: 'Autocomplete' }, (elementPath) => { const element = elementPath.node; + + element.openingElement.attributes.forEach((attribute, index) => { + if (attribute.type !== 'JSXAttribute' || attribute.name.name !== 'renderTags') { + return; + } + + attribute.name.name = 'renderValue'; + + if ( + attribute.value?.type === 'JSXExpressionContainer' && + (attribute.value.expression.type === 'ArrowFunctionExpression' || + attribute.value.expression.type === 'FunctionExpression') + ) { + renameRenderTagsCallback( + j, + elementPath.get('openingElement', 'attributes', index, 'value', 'expression'), + ); + } + }); + const propIndex = element.openingElement.attributes.findIndex( (attr) => attr.type === 'JSXAttribute' && attr.name.name === 'ListboxComponent', ); @@ -169,5 +282,13 @@ export default function transformer(file, api, options) { path.prune(); }); + defaultPropsPathCollection + .find(j.ObjectProperty, { key: { name: 'renderTags' } }) + .forEach((path) => { + renameRenderTagsProp(j, path); + }); + + renameUseAutocompleteReturnMembers(j, root); + return root.toSource(printOptions); } diff --git a/packages/mui-codemod/src/deprecations/autocomplete-props/test-cases/actual.js b/packages/mui-codemod/src/deprecations/autocomplete-props/test-cases/actual.js index 76a939df4f9629..16f72231ca9b5b 100644 --- a/packages/mui-codemod/src/deprecations/autocomplete-props/test-cases/actual.js +++ b/packages/mui-codemod/src/deprecations/autocomplete-props/test-cases/actual.js @@ -1,5 +1,7 @@ import Autocomplete from '@mui/material/Autocomplete'; import {Autocomplete as MyAutocomplete} from '@mui/material'; +import Chip from '@mui/material/Chip'; +import useAutocomplete from '@mui/material/useAutocomplete'; +/>; + + + value.map((option, index) => ( + + )) + } +/>; + +const { getTagProps, focusedTag } = useAutocomplete(props); + +; + +const { getTagProps: getAutocompleteTagProps, focusedTag: focusedAutocompleteTag } = + useAutocomplete(props); + +; diff --git a/packages/mui-codemod/src/deprecations/autocomplete-props/test-cases/expected.js b/packages/mui-codemod/src/deprecations/autocomplete-props/test-cases/expected.js index f18d47190402d5..eba5aef99dfe09 100644 --- a/packages/mui-codemod/src/deprecations/autocomplete-props/test-cases/expected.js +++ b/packages/mui-codemod/src/deprecations/autocomplete-props/test-cases/expected.js @@ -1,5 +1,7 @@ import Autocomplete from '@mui/material/Autocomplete'; import {Autocomplete as MyAutocomplete} from '@mui/material'; +import Chip from '@mui/material/Chip'; +import useAutocomplete from '@mui/material/useAutocomplete'; +/>; + + + value.map((option, index) => ( + + )) + } +/>; + +const { getItemProps, focusedItem } = useAutocomplete(props); + +; + +const { getItemProps: getAutocompleteTagProps, focusedItem: focusedAutocompleteTag } = + useAutocomplete(props); + +; diff --git a/packages/mui-codemod/src/deprecations/autocomplete-props/test-cases/package.actual.js b/packages/mui-codemod/src/deprecations/autocomplete-props/test-cases/package.actual.js index 482b767c1e37e9..281b9706ce4747 100644 --- a/packages/mui-codemod/src/deprecations/autocomplete-props/test-cases/package.actual.js +++ b/packages/mui-codemod/src/deprecations/autocomplete-props/test-cases/package.actual.js @@ -1,5 +1,6 @@ import Autocomplete from '@org/ui/material/Autocomplete'; import {Autocomplete as MyAutocomplete} from '@org/ui/material'; +import Chip from '@org/ui/material/Chip'; ; + + + value.map((option, index) => ) + } /> diff --git a/packages/mui-codemod/src/deprecations/autocomplete-props/test-cases/package.expected.js b/packages/mui-codemod/src/deprecations/autocomplete-props/test-cases/package.expected.js index 32da3ab85fd8b3..f6b88c26bd9d6f 100644 --- a/packages/mui-codemod/src/deprecations/autocomplete-props/test-cases/package.expected.js +++ b/packages/mui-codemod/src/deprecations/autocomplete-props/test-cases/package.expected.js @@ -1,5 +1,6 @@ import Autocomplete from '@org/ui/material/Autocomplete'; import {Autocomplete as MyAutocomplete} from '@org/ui/material'; +import Chip from '@org/ui/material/Chip'; ; + + + value.map((option, index) => ) + } /> diff --git a/packages/mui-codemod/src/deprecations/autocomplete-props/test-cases/theme.actual.js b/packages/mui-codemod/src/deprecations/autocomplete-props/test-cases/theme.actual.js index 5b144b5440188b..09956b98a59946 100644 --- a/packages/mui-codemod/src/deprecations/autocomplete-props/test-cases/theme.actual.js +++ b/packages/mui-codemod/src/deprecations/autocomplete-props/test-cases/theme.actual.js @@ -6,6 +6,10 @@ fn({ PopperComponent: CustomPopper, ListboxComponent: CustomListbox, ListboxProps: { height: 12 }, + renderTags: (value, getTagProps, ownerState) => + value.map((option, index) => ( + + )), componentsProps: { clearIndicator: { width: 10 }, paper: { width: 12 }, @@ -24,6 +28,10 @@ fn({ PopperComponent: CustomPopper, ListboxComponent: CustomListbox, ListboxProps: { height: 12 }, + renderTags: (value, getTagProps, ownerState) => + value.map((option, index) => ( + + )), slotProps: { popupIndicator: { width: 20 } }, diff --git a/packages/mui-codemod/src/deprecations/autocomplete-props/test-cases/theme.expected.js b/packages/mui-codemod/src/deprecations/autocomplete-props/test-cases/theme.expected.js index 93e6dcda392428..92aa80250e147e 100644 --- a/packages/mui-codemod/src/deprecations/autocomplete-props/test-cases/theme.expected.js +++ b/packages/mui-codemod/src/deprecations/autocomplete-props/test-cases/theme.expected.js @@ -1,6 +1,11 @@ fn({ MuiAutocomplete: { defaultProps: { + renderValue: (value, getItemProps, ownerState) => + value.map((option, index) => ( + + )), + slots: { paper: CustomPaper, popper: CustomPopper @@ -25,6 +30,11 @@ fn({ fn({ MuiAutocomplete: { defaultProps: { + renderValue: (value, getItemProps, ownerState) => + value.map((option, index) => ( + + )), + slotProps: { clearIndicator: { width: 10 }, paper: { width: 12 },