diff --git a/packages/x-charts/src/BarChart/checkScaleErrors.test.ts b/packages/x-charts/src/BarChart/checkScaleErrors.test.ts index 6e780a309ad3..219e6f6d1a47 100644 --- a/packages/x-charts/src/BarChart/checkScaleErrors.test.ts +++ b/packages/x-charts/src/BarChart/checkScaleErrors.test.ts @@ -22,7 +22,7 @@ describe('BarChart - checkScaleErrors', () => { }, ); }).throws( - 'MUI X Charts: The first `xAxis` should be of type "band" to display the bar series of id "seriesId".', + 'MUI X: The first `xAxis` should be of type "band" to display the bar series of id "seriesId".', ); }); @@ -43,7 +43,7 @@ describe('BarChart - checkScaleErrors', () => { [yKey]: { id: yKey, scaleType: 'linear' }, }, ); - }).throws('MUI X Charts: The first `xAxis` should have data property.'); + }).throws('MUI X: The first `xAxis` should have data property.'); }); it('should throw an error when the y-axis is not a continuous scale', () => { @@ -64,7 +64,7 @@ describe('BarChart - checkScaleErrors', () => { }, ); }).throws( - 'MUI X Charts: The first `yAxis` should be a continuous type to display the bar series of id "seriesId".', + 'MUI X: The first `yAxis` should be a continuous type to display the bar series of id "seriesId".', ); }); @@ -108,7 +108,7 @@ describe('BarChart - checkScaleErrors', () => { }, ); }).throws( - 'MUI X Charts: The first `yAxis` should be of type "band" to display the bar series of id "seriesId".', + 'MUI X: The first `yAxis` should be of type "band" to display the bar series of id "seriesId".', ); }); @@ -129,7 +129,7 @@ describe('BarChart - checkScaleErrors', () => { [yKey]: { id: yKey, scaleType: 'band' }, }, ); - }).throws('MUI X Charts: The first `yAxis` should have data property.'); + }).throws('MUI X: The first `yAxis` should have data property.'); }); it('should throw an error when the x-axis is not a continuous scale', () => { @@ -150,7 +150,7 @@ describe('BarChart - checkScaleErrors', () => { }, ); }).throws( - 'MUI X Charts: The first `xAxis` should be a continuous type to display the bar series of id "seriesId".', + 'MUI X: The first `xAxis` should be a continuous type to display the bar series of id "seriesId".', ); }); @@ -193,7 +193,7 @@ describe('BarChart - checkScaleErrors', () => { }, ); }).throws( - 'MUI X Charts: The x-axis with id "x-test" should be of type "band" to display the bar series of id "seriesId".', + 'MUI X: The x-axis with id "x-test" should be of type "band" to display the bar series of id "seriesId".', ); }); @@ -215,7 +215,7 @@ describe('BarChart - checkScaleErrors', () => { }, ); }).throws( - 'MUI X Charts: The y-axis with id "y-test" should be of type "band" to display the bar series of id "seriesId".', + 'MUI X: The y-axis with id "y-test" should be of type "band" to display the bar series of id "seriesId".', ); }); }); diff --git a/packages/x-charts/src/BarChart/checkScaleErrors.ts b/packages/x-charts/src/BarChart/checkScaleErrors.ts index cbd3137b11d5..62551a2c54c1 100644 --- a/packages/x-charts/src/BarChart/checkScaleErrors.ts +++ b/packages/x-charts/src/BarChart/checkScaleErrors.ts @@ -33,17 +33,17 @@ export function checkScaleErrors( if (!isBandScaleConfig(discreteAxisConfig)) { throw new Error( - `MUI X Charts: ${getAxisMessage(discreteAxisDirection, discreteAxisKey)} should be of type "band" to display the bar series of id "${seriesId}".`, + `MUI X: ${getAxisMessage(discreteAxisDirection, discreteAxisKey)} should be of type "band" to display the bar series of id "${seriesId}".`, ); } if (discreteAxisConfig.data === undefined) { throw new Error( - `MUI X Charts: ${getAxisMessage(discreteAxisDirection, discreteAxisKey)} should have data property.`, + `MUI X: ${getAxisMessage(discreteAxisDirection, discreteAxisKey)} should have data property.`, ); } if (isBandScaleConfig(continuousAxisConfig) || isPointScaleConfig(continuousAxisConfig)) { throw new Error( - `MUI X Charts: ${getAxisMessage(continuousAxisDirection, continuousAxisKey)} should be a continuous type to display the bar series of id "${seriesId}".`, + `MUI X: ${getAxisMessage(continuousAxisDirection, continuousAxisKey)} should be a continuous type to display the bar series of id "${seriesId}".`, ); } } diff --git a/packages/x-charts/src/BarChart/formatter.ts b/packages/x-charts/src/BarChart/formatter.ts index 362f58e1d910..2bc0ce692b15 100644 --- a/packages/x-charts/src/BarChart/formatter.ts +++ b/packages/x-charts/src/BarChart/formatter.ts @@ -33,7 +33,7 @@ const formatter: Formatter<'bar'> = (params, dataset) => { } else if (dataset === undefined) { throw new Error( [ - `MUI X Charts: bar series with id='${id}' has no data.`, + `MUI X: bar series with id='${id}' has no data.`, 'Either provide a data property to the series or use the dataset prop.', ].join('\n'), ); @@ -69,10 +69,12 @@ const formatter: Formatter<'bar'> = (params, dataset) => { if (typeof value !== 'number') { if (process.env.NODE_ENV !== 'production' && !warnOnce && value !== null) { warnOnce = true; - console.error([ - `MUI X charts: your dataset key "${dataKey}" is used for plotting bars, but contains nonnumerical elements.`, - 'Bar plots only support numbers and null values.', - ]); + console.error( + [ + `MUI X charts: your dataset key "${dataKey}" is used for plotting bars, but contains nonnumerical elements.`, + 'Bar plots only support numbers and null values.', + ].join('\n'), + ); } return 0; } diff --git a/packages/x-charts/src/ChartsAxis/ChartsAxis.tsx b/packages/x-charts/src/ChartsAxis/ChartsAxis.tsx index 4212e2950954..7ebbf743930a 100644 --- a/packages/x-charts/src/ChartsAxis/ChartsAxis.tsx +++ b/packages/x-charts/src/ChartsAxis/ChartsAxis.tsx @@ -99,7 +99,7 @@ function ChartsAxis(props: ChartsAxisProps) { if (topId !== null && !xAxis[topId]) { throw Error( [ - `MUI X Charts: id used for top axis "${topId}" is not defined.`, + `MUI X: id used for top axis "${topId}" is not defined.`, `Available ids are: ${xAxisIds.join(', ')}.`, ].join('\n'), ); @@ -107,7 +107,7 @@ function ChartsAxis(props: ChartsAxisProps) { if (leftId !== null && !yAxis[leftId]) { throw Error( [ - `MUI X Charts: id used for left axis "${leftId}" is not defined.`, + `MUI X: id used for left axis "${leftId}" is not defined.`, `Available ids are: ${yAxisIds.join(', ')}.`, ].join('\n'), ); @@ -115,7 +115,7 @@ function ChartsAxis(props: ChartsAxisProps) { if (rightId !== null && !yAxis[rightId]) { throw Error( [ - `MUI X Charts: id used for right axis "${rightId}" is not defined.`, + `MUI X: id used for right axis "${rightId}" is not defined.`, `Available ids are: ${yAxisIds.join(', ')}.`, ].join('\n'), ); @@ -123,7 +123,7 @@ function ChartsAxis(props: ChartsAxisProps) { if (bottomId !== null && !xAxis[bottomId]) { throw Error( [ - `MUI X Charts: id used for bottom axis "${bottomId}" is not defined.`, + `MUI X: id used for bottom axis "${bottomId}" is not defined.`, `Available ids are: ${xAxisIds.join(', ')}.`, ].join('\n'), ); diff --git a/packages/x-charts/src/ChartsAxisHighlight/ChartsAxisHighlight.tsx b/packages/x-charts/src/ChartsAxisHighlight/ChartsAxisHighlight.tsx index 38f77bcc6b36..456067392205 100644 --- a/packages/x-charts/src/ChartsAxisHighlight/ChartsAxisHighlight.tsx +++ b/packages/x-charts/src/ChartsAxisHighlight/ChartsAxisHighlight.tsx @@ -94,7 +94,7 @@ function ChartsAxisHighlight(props: ChartsAxisHighlightProps) { if (isXError || isYError) { console.error( [ - `MUI X Charts: The position value provided for the axis is not valid for the current scale.`, + `MUI X: The position value provided for the axis is not valid for the current scale.`, `This probably means something is wrong with the data passed to the chart.`, `The ChartsAxisHighlight component will not be displayed.`, ].join('\n'), diff --git a/packages/x-charts/src/ChartsReferenceLine/ChartsReferenceLine.tsx b/packages/x-charts/src/ChartsReferenceLine/ChartsReferenceLine.tsx index bf8d673866d5..1826282a9820 100644 --- a/packages/x-charts/src/ChartsReferenceLine/ChartsReferenceLine.tsx +++ b/packages/x-charts/src/ChartsReferenceLine/ChartsReferenceLine.tsx @@ -12,15 +12,11 @@ type ChartsReferenceLineProps - `MUI X Charts: the value ${value} does not exist in the data of x axis with id ${id}.`, - 'error', -); - function ChartsXReferenceLine(props: ChartsXReferenceLineProps) { const { x, @@ -98,7 +92,10 @@ function ChartsXReferenceLine(props: ChartsXReferenceLineProps) { if (xPosition === undefined) { if (process.env.NODE_ENV !== 'production') { - valueError(x, axisId); + warnOnce( + `MUI X: the value ${x} does not exist in the data of x axis with id ${axisId}.`, + 'error', + ); } return null; } diff --git a/packages/x-charts/src/ChartsReferenceLine/ChartsYReferenceLine.tsx b/packages/x-charts/src/ChartsReferenceLine/ChartsYReferenceLine.tsx index 048c37c3459f..affaeb867e71 100644 --- a/packages/x-charts/src/ChartsReferenceLine/ChartsYReferenceLine.tsx +++ b/packages/x-charts/src/ChartsReferenceLine/ChartsYReferenceLine.tsx @@ -7,7 +7,7 @@ import { ChartsReferenceLineClasses, getReferenceLineUtilityClass, } from './chartsReferenceLineClasses'; -import { buildWarning } from '../internals/warning'; +import { warnOnce } from '../internals/warning'; export type ChartsYReferenceLineProps< TValue extends string | number | Date = string | number | Date, @@ -73,12 +73,6 @@ export function getYReferenceLineClasses(classes?: Partial - `MUI X Charts: the value ${value} does not exist in the data of y axis with id ${id}.`, - 'error', -); - function ChartsYReferenceLine(props: ChartsYReferenceLineProps) { const { y, @@ -98,7 +92,10 @@ function ChartsYReferenceLine(props: ChartsYReferenceLineProps) { if (yPosition === undefined) { if (process.env.NODE_ENV !== 'production') { - valueError(y, axisId); + warnOnce( + `MUI X: the value ${y} does not exist in the data of y axis with id ${axisId}.`, + 'error', + ); } return null; } diff --git a/packages/x-charts/src/LineChart/AreaPlot.tsx b/packages/x-charts/src/LineChart/AreaPlot.tsx index edf60f0893ad..ca498224b029 100644 --- a/packages/x-charts/src/LineChart/AreaPlot.tsx +++ b/packages/x-charts/src/LineChart/AreaPlot.tsx @@ -71,7 +71,7 @@ const useAggregatedData = () => { if (process.env.NODE_ENV !== 'production') { if (xData === undefined) { throw new Error( - `MUI X Charts: ${ + `MUI X: ${ xAxisKey === DEFAULT_X_AXIS_KEY ? 'The first `xAxis`' : `The x-axis with id "${xAxisKey}"` @@ -80,7 +80,7 @@ const useAggregatedData = () => { } if (xData.length < stackedData.length) { throw new Error( - `MUI X Charts: The data length of the x axis (${xData.length} items) is lower than the length of series (${stackedData.length} items).`, + `MUI X: The data length of the x axis (${xData.length} items) is lower than the length of series (${stackedData.length} items).`, ); } } diff --git a/packages/x-charts/src/LineChart/LineHighlightPlot.tsx b/packages/x-charts/src/LineChart/LineHighlightPlot.tsx index a8121982a01b..2edd0d7048d1 100644 --- a/packages/x-charts/src/LineChart/LineHighlightPlot.tsx +++ b/packages/x-charts/src/LineChart/LineHighlightPlot.tsx @@ -84,7 +84,7 @@ function LineHighlightPlot(props: LineHighlightPlotProps) { if (xData === undefined) { throw new Error( - `MUI X Charts: ${ + `MUI X: ${ xAxisKey === DEFAULT_X_AXIS_KEY ? 'The first `xAxis`' : `The x-axis with id "${xAxisKey}"` diff --git a/packages/x-charts/src/LineChart/LinePlot.tsx b/packages/x-charts/src/LineChart/LinePlot.tsx index 6ee5d39eabf1..1be96548b3cd 100644 --- a/packages/x-charts/src/LineChart/LinePlot.tsx +++ b/packages/x-charts/src/LineChart/LinePlot.tsx @@ -69,7 +69,7 @@ const useAggregatedData = () => { if (process.env.NODE_ENV !== 'production') { if (xData === undefined) { throw new Error( - `MUI X Charts: ${ + `MUI X: ${ xAxisKey === DEFAULT_X_AXIS_KEY ? 'The first `xAxis`' : `The x-axis with id "${xAxisKey}"` @@ -78,7 +78,7 @@ const useAggregatedData = () => { } if (xData.length < stackedData.length) { throw new Error( - `MUI X Charts: The data length of the x axis (${xData.length} items) is lower than the length of series (${stackedData.length} items).`, + `MUI X: The data length of the x axis (${xData.length} items) is lower than the length of series (${stackedData.length} items).`, ); } } diff --git a/packages/x-charts/src/LineChart/MarkPlot.tsx b/packages/x-charts/src/LineChart/MarkPlot.tsx index 12c52119dc3d..b030c49ebf13 100644 --- a/packages/x-charts/src/LineChart/MarkPlot.tsx +++ b/packages/x-charts/src/LineChart/MarkPlot.tsx @@ -93,7 +93,7 @@ function MarkPlot(props: MarkPlotProps) { if (xData === undefined) { throw new Error( - `MUI X Charts: ${ + `MUI X: ${ xAxisKey === DEFAULT_X_AXIS_KEY ? 'The first `xAxis`' : `The x-axis with id "${xAxisKey}"` diff --git a/packages/x-charts/src/LineChart/formatter.ts b/packages/x-charts/src/LineChart/formatter.ts index 53962140313b..168eb29c0a8d 100644 --- a/packages/x-charts/src/LineChart/formatter.ts +++ b/packages/x-charts/src/LineChart/formatter.ts @@ -32,7 +32,7 @@ const formatter: Formatter<'line'> = (params, dataset) => { } else if (dataset === undefined && process.env.NODE_ENV !== 'production') { throw new Error( [ - `MUI X Charts: line series with id='${id}' has no data.`, + `MUI X: line series with id='${id}' has no data.`, 'Either provide a data property to the series or use the dataset prop.', ].join('\n'), ); @@ -67,7 +67,7 @@ const formatter: Formatter<'line'> = (params, dataset) => { if (process.env.NODE_ENV !== 'production' && !warnedOnce && value !== null) { warnedOnce = true; console.error([ - `MUI X charts: your dataset key "${dataKey}" is used for plotting line, but contains nonnumerical elements.`, + `MUI X: Your dataset key "${dataKey}" is used for plotting line, but contains nonnumerical elements.`, 'Line plots only support numbers and null values.', ]); } diff --git a/packages/x-charts/src/ResponsiveChartContainer/useChartContainerDimensions.ts b/packages/x-charts/src/ResponsiveChartContainer/useChartContainerDimensions.ts index cb6001d1588f..00ba813b4a28 100644 --- a/packages/x-charts/src/ResponsiveChartContainer/useChartContainerDimensions.ts +++ b/packages/x-charts/src/ResponsiveChartContainer/useChartContainerDimensions.ts @@ -69,13 +69,13 @@ export const useChartContainerDimensions = (inWidth?: number, inHeight?: number) if (process.env.NODE_ENV !== 'production') { if (displayError.current && inWidth === undefined && width === 0) { console.error( - `MUI X Charts: ChartContainer does not have \`width\` prop, and its container has no \`width\` defined.`, + `MUI X: ChartContainer does not have \`width\` prop, and its container has no \`width\` defined.`, ); displayError.current = false; } if (displayError.current && inHeight === undefined && height === 0) { console.error( - `MUI X Charts: ChartContainer does not have \`height\` prop, and its container has no \`height\` defined.`, + `MUI X: ChartContainer does not have \`height\` prop, and its container has no \`height\` defined.`, ); displayError.current = false; } diff --git a/packages/x-charts/src/context/CartesianProvider/normalizeAxis.ts b/packages/x-charts/src/context/CartesianProvider/normalizeAxis.ts index 36659a061f66..89f4eefc9518 100644 --- a/packages/x-charts/src/context/CartesianProvider/normalizeAxis.ts +++ b/packages/x-charts/src/context/CartesianProvider/normalizeAxis.ts @@ -18,9 +18,7 @@ export const normalizeAxis = < return axisConfig; } if (dataset === undefined) { - throw Error( - `MUI X Charts: ${axisName}-axis uses \`dataKey\` but no \`dataset\` is provided.`, - ); + throw Error(`MUI X: ${axisName}-axis uses \`dataKey\` but no \`dataset\` is provided.`); } return { ...axisConfig, diff --git a/packages/x-charts/src/context/SeriesContextProvider.tsx b/packages/x-charts/src/context/SeriesContextProvider.tsx index 05ee76e3bf28..8b27ea3b155d 100644 --- a/packages/x-charts/src/context/SeriesContextProvider.tsx +++ b/packages/x-charts/src/context/SeriesContextProvider.tsx @@ -76,7 +76,7 @@ const preprocessSeries = ( seriesGroups[type] = { series: {}, seriesOrder: [] }; } if (seriesGroups[type]?.series[id] !== undefined) { - throw new Error(`MUI X Charts: series' id "${id}" is not unique.`); + throw new Error(`MUI X: series' id "${id}" is not unique.`); } seriesGroups[type]!.series[id] = { diff --git a/packages/x-charts/src/context/ZAxisContextProvider.tsx b/packages/x-charts/src/context/ZAxisContextProvider.tsx index 413dc0fad32f..d494e3513caa 100644 --- a/packages/x-charts/src/context/ZAxisContextProvider.tsx +++ b/packages/x-charts/src/context/ZAxisContextProvider.tsx @@ -47,7 +47,7 @@ function ZAxisContextProvider(props: ZAxisContextProviderProps) { return axisConfig; } if (dataset === undefined) { - throw Error('MUI X Charts: z-axis uses `dataKey` but no `dataset` is provided.'); + throw Error('MUI X: z-axis uses `dataKey` but no `dataset` is provided.'); } return { ...axisConfig, diff --git a/packages/x-charts/src/internals/geometry.ts b/packages/x-charts/src/internals/geometry.ts index 593ba18e20a0..43fd024cacc3 100644 --- a/packages/x-charts/src/internals/geometry.ts +++ b/packages/x-charts/src/internals/geometry.ts @@ -16,7 +16,7 @@ export function getMinXTranslation(width: number, height: number, angle: number warnedOnce = true; console.warn( [ - `MUI X Charts: It seems you applied an angle larger than 90° or smaller than -90° to an axis text.`, + `MUI X: It seems you applied an angle larger than 90° or smaller than -90° to an axis text.`, `This could cause some text overlapping.`, `If you encounter a use case where it's needed, please open an issue.`, ].join('\n'), diff --git a/packages/x-charts/src/internals/getPercentageValue.ts b/packages/x-charts/src/internals/getPercentageValue.ts index 2edabcbd93c5..85f843f10852 100644 --- a/packages/x-charts/src/internals/getPercentageValue.ts +++ b/packages/x-charts/src/internals/getPercentageValue.ts @@ -25,6 +25,6 @@ export function getPercentageValue(value: number | string, refValue: number) { } } throw Error( - `MUI X Charts: Received an unknown value "${value}". It should be a number, or a string with a percentage value.`, + `MUI X: Received an unknown value "${value}". It should be a number, or a string with a percentage value.`, ); } diff --git a/packages/x-charts/src/internals/warning.ts b/packages/x-charts/src/internals/warning.ts index 412c7ed444dd..f0d34fc59f68 100644 --- a/packages/x-charts/src/internals/warning.ts +++ b/packages/x-charts/src/internals/warning.ts @@ -1,17 +1,25 @@ -export function buildWarning( - message: (...args: any) => string, - gravity: 'warning' | 'error' = 'warning', -) { - let alreadyWarned = false; +const warnedOnceCache = new Set(); - return (...args: any) => { - if (!alreadyWarned) { - alreadyWarned = true; - if (gravity === 'error') { - console.error(message(...args)); - } else { - console.warn(message(...args)); - } +// TODO move to @mui/x-internals +// TODO eventually move to @base_ui/internals. Base UI, etc. too need this helper. +export function warnOnce(message: string | string[], gravity: 'warning' | 'error' = 'warning') { + if (process.env.NODE_ENV === 'production') { + return; + } + + const cleanMessage = Array.isArray(message) ? message.join('\n') : message; + + if (!warnedOnceCache.has(cleanMessage)) { + warnedOnceCache.add(cleanMessage); + + if (gravity === 'error') { + console.error(cleanMessage); + } else { + console.warn(cleanMessage); } - }; + } +} + +export function clearWarningsCache() { + warnedOnceCache.clear(); } diff --git a/packages/x-data-grid-premium/src/hooks/features/clipboard/useGridClipboardImport.ts b/packages/x-data-grid-premium/src/hooks/features/clipboard/useGridClipboardImport.ts index b05389a3fd0c..372832f927e7 100644 --- a/packages/x-data-grid-premium/src/hooks/features/clipboard/useGridClipboardImport.ts +++ b/packages/x-data-grid-premium/src/hooks/features/clipboard/useGridClipboardImport.ts @@ -14,7 +14,7 @@ import { gridExpandedSortedRowIdsSelector, } from '@mui/x-data-grid'; import { - buildWarning, + warnOnce, getRowIdFromRowModel, getActiveElement, GridPipeProcessor, @@ -28,15 +28,6 @@ import { unstable_debounce as debounce } from '@mui/utils'; import { GridApiPremium, GridPrivateApiPremium } from '../../../models/gridApiPremium'; import type { DataGridPremiumProcessedProps } from '../../../models/dataGridPremiumProps'; -const missingOnProcessRowUpdateErrorWarning = buildWarning( - [ - 'MUI X: A call to `processRowUpdate` threw an error which was not handled because `onProcessRowUpdateError` is missing.', - 'To handle the error pass a callback to the `onProcessRowUpdateError` prop, for example ` ...} />`.', - 'For more detail, see https://mui.com/x/react-data-grid/editing/#server-side-persistence.', - ], - 'error', -); - const columnFieldsToExcludeFromPaste = [ GRID_CHECKBOX_SELECTION_FIELD, GRID_REORDER_COL_DEF.field, @@ -176,7 +167,14 @@ class CellValueUpdater { if (onProcessRowUpdateError) { onProcessRowUpdateError(errorThrown); } else if (process.env.NODE_ENV !== 'production') { - missingOnProcessRowUpdateErrorWarning(); + warnOnce( + [ + 'MUI X: A call to `processRowUpdate` threw an error which was not handled because `onProcessRowUpdateError` is missing.', + 'To handle the error pass a callback to the `onProcessRowUpdateError` prop, for example ` ...} />`.', + 'For more detail, see https://mui.com/x/react-data-grid/editing/#server-side-persistence.', + ], + 'error', + ); } }; diff --git a/packages/x-data-grid-premium/src/hooks/features/export/serializer/excelSerializer.ts b/packages/x-data-grid-premium/src/hooks/features/export/serializer/excelSerializer.ts index f6c9de2adcc1..3ab0b46dedb3 100644 --- a/packages/x-data-grid-premium/src/hooks/features/export/serializer/excelSerializer.ts +++ b/packages/x-data-grid-premium/src/hooks/features/export/serializer/excelSerializer.ts @@ -9,7 +9,7 @@ import { GridValidRowModel, } from '@mui/x-data-grid-pro'; import { - buildWarning, + warnOnce, GridStateColDef, GridSingleSelectColDef, isObject, @@ -25,11 +25,6 @@ const getExcelJs = async () => { return excelJsModule.default ?? excelJsModule; }; -const warnInvalidFormattedValue = buildWarning([ - 'MUI X: When the value of a field is an object or a `renderCell` is provided, the Excel export might not display the value correctly.', - 'You can provide a `valueFormatter` with a string representation to be used.', -]); - const getFormattedValueOptions = ( colDef: GridSingleSelectColDef, row: GridValidRowModel, @@ -151,7 +146,10 @@ export const serializeRowUnsafe = ( const formattedValue = apiRef.current.getCellParams(id, castColumn.field).formattedValue; if (process.env.NODE_ENV !== 'production') { if (String(cellParams.formattedValue) === '[object Object]') { - warnInvalidFormattedValue(); + warnOnce([ + 'MUI X: When the value of a field is an object or a `renderCell` is provided, the Excel export might not display the value correctly.', + 'You can provide a `valueFormatter` with a string representation to be used.', + ]); } } if (isObject<{ label: any }>(formattedValue)) { @@ -194,7 +192,10 @@ export const serializeRowUnsafe = ( cellValue = apiRef.current.getCellParams(id, column.field).formattedValue as any; if (process.env.NODE_ENV !== 'production') { if (String(cellParams.formattedValue) === '[object Object]') { - warnInvalidFormattedValue(); + warnOnce([ + 'MUI X: When the value of a field is an object or a `renderCell` is provided, the Excel export might not display the value correctly.', + 'You can provide a `valueFormatter` with a string representation to be used.', + ]); } } break; diff --git a/packages/x-data-grid/src/hooks/features/editing/useGridCellEditing.ts b/packages/x-data-grid/src/hooks/features/editing/useGridCellEditing.ts index 8220068b7d35..fa6afb47afaf 100644 --- a/packages/x-data-grid/src/hooks/features/editing/useGridCellEditing.ts +++ b/packages/x-data-grid/src/hooks/features/editing/useGridCellEditing.ts @@ -30,7 +30,7 @@ import { useGridApiMethod } from '../../utils/useGridApiMethod'; import { gridEditRowsStateSelector } from './gridEditingSelectors'; import { GridRowId } from '../../../models/gridRows'; import { isPrintableKey, isPasteShortcut } from '../../../utils/keyboardUtils'; -import { buildWarning } from '../../../utils/warning'; +import { warnOnce } from '../../../internals/utils/warning'; import { gridRowsDataRowIdToIdLookupSelector } from '../rows/gridRowsSelector'; import { deepClone } from '../../../utils/utils'; import { @@ -40,15 +40,6 @@ import { GridCellEditStopReasons, } from '../../../models/params/gridEditCellParams'; -const missingOnProcessRowUpdateErrorWarning = buildWarning( - [ - 'MUI X: A call to `processRowUpdate` threw an error which was not handled because `onProcessRowUpdateError` is missing.', - 'To handle the error pass a callback to the `onProcessRowUpdateError` prop, for example ` ...} />`.', - 'For more detail, see https://mui.com/x/react-data-grid/editing/#server-side-persistence.', - ], - 'error', -); - export const useGridCellEditing = ( apiRef: React.MutableRefObject, props: Pick< @@ -435,7 +426,14 @@ export const useGridCellEditing = ( if (onProcessRowUpdateError) { onProcessRowUpdateError(errorThrown); } else if (process.env.NODE_ENV !== 'production') { - missingOnProcessRowUpdateErrorWarning(); + warnOnce( + [ + 'MUI X: A call to `processRowUpdate` threw an error which was not handled because `onProcessRowUpdateError` is missing.', + 'To handle the error pass a callback to the `onProcessRowUpdateError` prop, for example ` ...} />`.', + 'For more detail, see https://mui.com/x/react-data-grid/editing/#server-side-persistence.', + ], + 'error', + ); } }; diff --git a/packages/x-data-grid/src/hooks/features/editing/useGridRowEditing.ts b/packages/x-data-grid/src/hooks/features/editing/useGridRowEditing.ts index 33932d80672f..fe07ea384681 100644 --- a/packages/x-data-grid/src/hooks/features/editing/useGridRowEditing.ts +++ b/packages/x-data-grid/src/hooks/features/editing/useGridRowEditing.ts @@ -36,7 +36,7 @@ import { gridVisibleColumnFieldsSelector, } from '../columns/gridColumnsSelector'; import { GridCellParams } from '../../../models/params/gridCellParams'; -import { buildWarning } from '../../../utils/warning'; +import { warnOnce } from '../../../internals/utils/warning'; import { gridRowsDataRowIdToIdLookupSelector } from '../rows/gridRowsSelector'; import { deepClone } from '../../../utils/utils'; import { @@ -47,15 +47,6 @@ import { } from '../../../models/params/gridRowParams'; import { GRID_ACTIONS_COLUMN_TYPE } from '../../../colDef'; -const missingOnProcessRowUpdateErrorWarning = buildWarning( - [ - 'MUI X: A call to `processRowUpdate` threw an error which was not handled because `onProcessRowUpdateError` is missing.', - 'To handle the error pass a callback to the `onProcessRowUpdateError` prop, for example ` ...} />`.', - 'For more detail, see https://mui.com/x/react-data-grid/editing/#server-side-persistence.', - ], - 'error', -); - export const useGridRowEditing = ( apiRef: React.MutableRefObject, props: Pick< @@ -522,7 +513,14 @@ export const useGridRowEditing = ( if (onProcessRowUpdateError) { onProcessRowUpdateError(errorThrown); } else if (process.env.NODE_ENV !== 'production') { - missingOnProcessRowUpdateErrorWarning(); + warnOnce( + [ + 'MUI X: A call to `processRowUpdate` threw an error which was not handled because `onProcessRowUpdateError` is missing.', + 'To handle the error pass a callback to the `onProcessRowUpdateError` prop, for example ` ...} />`.', + 'For more detail, see https://mui.com/x/react-data-grid/editing/#server-side-persistence.', + ], + 'error', + ); } }; diff --git a/packages/x-data-grid/src/hooks/features/export/serializers/csvSerializer.ts b/packages/x-data-grid/src/hooks/features/export/serializers/csvSerializer.ts index bc8b49658233..7df03e0600f5 100644 --- a/packages/x-data-grid/src/hooks/features/export/serializers/csvSerializer.ts +++ b/packages/x-data-grid/src/hooks/features/export/serializers/csvSerializer.ts @@ -3,7 +3,7 @@ import { GRID_CHECKBOX_SELECTION_COL_DEF } from '../../../../colDef'; import type { GridCellParams } from '../../../../models/params/gridCellParams'; import type { GridStateColDef } from '../../../../models/colDef/gridColDef'; import type { GridApiCommunity } from '../../../../models/api/gridApiCommunity'; -import { buildWarning } from '../../../../utils/warning'; +import { warnOnce } from '../../../../internals/utils/warning'; function sanitizeCellValue(value: unknown, csvOptions: CSVOptions): string { const valueStr = typeof value === 'string' ? value : `${value}`; @@ -53,11 +53,6 @@ export const serializeCellValue = ( return sanitizeCellValue(value, csvOptions); }; -const objectFormattedValueWarning = buildWarning([ - 'MUI X: When the value of a field is an object or a `renderCell` is provided, the CSV export might not display the value correctly.', - 'You can provide a `valueFormatter` with a string representation to be used.', -]); - type CSVOptions = Required< Pick >; @@ -115,7 +110,10 @@ const serializeRow = ({ const cellParams = getCellParams(id, column.field); if (process.env.NODE_ENV !== 'production') { if (String(cellParams.formattedValue) === '[object Object]') { - objectFormattedValueWarning(); + warnOnce([ + 'MUI X: When the value of a field is an object or a `renderCell` is provided, the CSV export might not display the value correctly.', + 'You can provide a `valueFormatter` with a string representation to be used.', + ]); } } row.addValue( diff --git a/packages/x-data-grid/src/hooks/features/filter/gridFilterUtils.ts b/packages/x-data-grid/src/hooks/features/filter/gridFilterUtils.ts index 028bc8c126af..5a6f64bce4dc 100644 --- a/packages/x-data-grid/src/hooks/features/filter/gridFilterUtils.ts +++ b/packages/x-data-grid/src/hooks/features/filter/gridFilterUtils.ts @@ -14,7 +14,7 @@ import { GridFilterItemResult, GridQuickFilterValueResult, } from './gridFilterState'; -import { buildWarning } from '../../../utils/warning'; +import { warnOnce } from '../../../internals/utils/warning'; import { getPublicApiRef } from '../../../utils/getPublicApiRef'; import { gridColumnFieldsSelector, @@ -75,24 +75,6 @@ export const cleanFilterItem = ( return cleanItem; }; -const filterModelDisableMultiColumnsFilteringWarning = buildWarning( - [ - 'MUI X: The `filterModel` can only contain a single item when the `disableMultipleColumnsFiltering` prop is set to `true`.', - 'If you are using the community version of the `DataGrid`, this prop is always `true`.', - ], - 'error', -); - -const filterModelMissingItemIdWarning = buildWarning( - 'MUI X: The `id` field is required on `filterModel.items` when you use multiple filters.', - 'error', -); - -const filterModelMissingItemOperatorWarning = buildWarning( - 'MUI X: The `operator` field is required on `filterModel.items`, one or more of your filtering item has no `operator` provided.', - 'error', -); - export const sanitizeFilterModel = ( model: GridFilterModel, disableMultipleColumnsFiltering: boolean, @@ -102,8 +84,15 @@ export const sanitizeFilterModel = ( let items: GridFilterItem[]; if (hasSeveralItems && disableMultipleColumnsFiltering) { - filterModelDisableMultiColumnsFilteringWarning(); - + if (process.env.NODE_ENV !== 'production') { + warnOnce( + [ + 'MUI X: The `filterModel` can only contain a single item when the `disableMultipleColumnsFiltering` prop is set to `true`.', + 'If you are using the community version of the `DataGrid`, this prop is always `true`.', + ], + 'error', + ); + } items = [model.items[0]]; } else { items = model.items; @@ -112,12 +101,22 @@ export const sanitizeFilterModel = ( const hasItemsWithoutIds = hasSeveralItems && items.some((item) => item.id == null); const hasItemWithoutOperator = items.some((item) => item.operator == null); - if (hasItemsWithoutIds) { - filterModelMissingItemIdWarning(); + if (process.env.NODE_ENV !== 'production') { + if (hasItemsWithoutIds) { + warnOnce( + 'MUI X: The `id` field is required on `filterModel.items` when you use multiple filters.', + 'error', + ); + } } - if (hasItemWithoutOperator) { - filterModelMissingItemOperatorWarning(); + if (process.env.NODE_ENV !== 'production') { + if (hasItemWithoutOperator) { + warnOnce( + 'MUI X: The `operator` field is required on `filterModel.items`, one or more of your filtering item has no `operator` provided.', + 'error', + ); + } } if (hasItemWithoutOperator || hasItemsWithoutIds) { diff --git a/packages/x-data-grid/src/hooks/features/sorting/gridSortingUtils.ts b/packages/x-data-grid/src/hooks/features/sorting/gridSortingUtils.ts index 77d6486988a5..ce7138653a28 100644 --- a/packages/x-data-grid/src/hooks/features/sorting/gridSortingUtils.ts +++ b/packages/x-data-grid/src/hooks/features/sorting/gridSortingUtils.ts @@ -10,7 +10,7 @@ import { GridSortModel, GridSortCellParams, } from '../../../models/gridSortModel'; -import { buildWarning } from '../../../utils/warning'; +import { warnOnce } from '../../../internals/utils/warning'; type GridSortingFieldComparator = { getSortCellParams: (id: GridRowId) => GridSortCellParams; @@ -22,18 +22,16 @@ interface GridParsedSortItem { getSortCellParams: (id: GridRowId) => GridSortCellParams; } -const sortModelDisableMultiColumnsSortingWarning = buildWarning( - [ - 'MUI X: The `sortModel` can only contain a single item when the `disableMultipleColumnsSorting` prop is set to `true`.', - 'If you are using the community version of the `DataGrid`, this prop is always `true`.', - ], - 'error', -); - export const sanitizeSortModel = (model: GridSortModel, disableMultipleColumnsSorting: boolean) => { if (disableMultipleColumnsSorting && model.length > 1) { if (process.env.NODE_ENV !== 'production') { - sortModelDisableMultiColumnsSortingWarning(); + warnOnce( + [ + 'MUI X: The `sortModel` can only contain a single item when the `disableMultipleColumnsSorting` prop is set to `true`.', + 'If you are using the community version of the `DataGrid`, this prop is always `true`.', + ], + 'error', + ); } return [model[0]]; } diff --git a/packages/x-data-grid/src/hooks/utils/useGridSelector.ts b/packages/x-data-grid/src/hooks/utils/useGridSelector.ts index 859167dcfb24..76fc6754b8c8 100644 --- a/packages/x-data-grid/src/hooks/utils/useGridSelector.ts +++ b/packages/x-data-grid/src/hooks/utils/useGridSelector.ts @@ -3,14 +3,9 @@ import type { GridApiCommon } from '../../models/api/gridApiCommon'; import { OutputSelector } from '../../utils/createSelector'; import { useLazyRef } from './useLazyRef'; import { useOnMount } from './useOnMount'; -import { buildWarning } from '../../utils/warning'; +import { warnOnce } from '../../internals/utils/warning'; import { fastObjectShallowCompare } from '../../utils/fastObjectShallowCompare'; -const stateNotInitializedWarning = buildWarning([ - 'MUI X: `useGridSelector` has been called before the initialization of the state.', - 'This hook can only be used inside the context of the grid.', -]); - function isOutputSelector( selector: any, ): selector is OutputSelector { @@ -39,7 +34,10 @@ export const useGridSelector = ( ) => { if (process.env.NODE_ENV !== 'production') { if (!apiRef.current.state) { - stateNotInitializedWarning(); + warnOnce([ + 'MUI X: `useGridSelector` has been called before the initialization of the state.', + 'This hook can only be used inside the context of the grid.', + ]); } } diff --git a/packages/x-data-grid/src/internals/index.ts b/packages/x-data-grid/src/internals/index.ts index 1387f50016d3..90f1ca592e48 100644 --- a/packages/x-data-grid/src/internals/index.ts +++ b/packages/x-data-grid/src/internals/index.ts @@ -143,7 +143,6 @@ export { export { isNavigationKey, isPasteShortcut } from '../utils/keyboardUtils'; export * from '../utils/utils'; export * from '../utils/fastMemo'; -export { buildWarning } from '../utils/warning'; export { exportAs } from '../utils/exportAs'; export * from '../utils/getPublicApiRef'; export * from '../utils/cellBorderUtils'; diff --git a/packages/x-data-grid/src/internals/utils/index.ts b/packages/x-data-grid/src/internals/utils/index.ts index 5bdfb7c8c66f..20749d11ceb9 100644 --- a/packages/x-data-grid/src/internals/utils/index.ts +++ b/packages/x-data-grid/src/internals/utils/index.ts @@ -1,3 +1,4 @@ export * from './computeSlots'; export * from './useProps'; export * from './propValidation'; +export * from './warning'; diff --git a/packages/x-data-grid/src/internals/utils/propValidation.ts b/packages/x-data-grid/src/internals/utils/propValidation.ts index 825db3991f71..e6dbaad64390 100644 --- a/packages/x-data-grid/src/internals/utils/propValidation.ts +++ b/packages/x-data-grid/src/internals/utils/propValidation.ts @@ -1,3 +1,4 @@ +import { warnOnce } from './warning'; import { isNumber } from '../../utils/utils'; import { DataGridProcessedProps } from '../../models/props/DataGridProps'; import { GridSignature } from '../../hooks/utils/useGridApiEventHandler'; @@ -43,23 +44,11 @@ export const propValidatorsDataGrid: PropValidator[] = [ undefined, ]; -const warnedOnceCache = new Set(); -function warnOnce(message: string) { - if (!warnedOnceCache.has(message)) { - console.error(message); - warnedOnceCache.add(message); - } -} - export function validateProps(props: TProps, validators: PropValidator[]) { validators.forEach((validator) => { - const warning = validator(props); - if (warning) { - warnOnce(warning); + const message = validator(props); + if (message) { + warnOnce(message, 'error'); } }); } - -export function clearWarningsCache() { - warnedOnceCache.clear(); -} diff --git a/packages/x-data-grid/src/internals/utils/warning.ts b/packages/x-data-grid/src/internals/utils/warning.ts new file mode 100644 index 000000000000..f0d34fc59f68 --- /dev/null +++ b/packages/x-data-grid/src/internals/utils/warning.ts @@ -0,0 +1,25 @@ +const warnedOnceCache = new Set(); + +// TODO move to @mui/x-internals +// TODO eventually move to @base_ui/internals. Base UI, etc. too need this helper. +export function warnOnce(message: string | string[], gravity: 'warning' | 'error' = 'warning') { + if (process.env.NODE_ENV === 'production') { + return; + } + + const cleanMessage = Array.isArray(message) ? message.join('\n') : message; + + if (!warnedOnceCache.has(cleanMessage)) { + warnedOnceCache.add(cleanMessage); + + if (gravity === 'error') { + console.error(cleanMessage); + } else { + console.warn(cleanMessage); + } + } +} + +export function clearWarningsCache() { + warnedOnceCache.clear(); +} diff --git a/packages/x-data-grid/src/utils/createSelector.ts b/packages/x-data-grid/src/utils/createSelector.ts index 7feb525a5a0a..121c17fd11b3 100644 --- a/packages/x-data-grid/src/utils/createSelector.ts +++ b/packages/x-data-grid/src/utils/createSelector.ts @@ -1,7 +1,7 @@ import * as React from 'react'; import { createSelector as reselectCreateSelector, Selector, SelectorResultArray } from 'reselect'; import type { GridCoreApi } from '../models/api/gridCoreApi'; -import { buildWarning } from './warning'; +import { warnOnce } from '../internals/utils/warning'; type CacheKey = { id: number }; @@ -38,11 +38,6 @@ type CreateSelectorFunction = >, R const cache = new WeakMap>(); -const missingInstanceIdWarning = buildWarning([ - 'MUI X: A selector was called without passing the instance ID, which may impact the performance of the grid.', - 'To fix, call it with `apiRef`, for example `mySelector(apiRef)`, or pass the instance ID explicitly, for example `mySelector(state, apiRef.current.instanceId)`.', -]); - function checkIsAPIRef(value: any) { return 'current' in value && 'instanceId' in value.current; } @@ -140,7 +135,10 @@ export const createSelectorMemoized: CreateSelectorFunction = (...args: any) => if (process.env.NODE_ENV !== 'production') { if (cacheKey.id === 'default') { - missingInstanceIdWarning(); + warnOnce([ + 'MUI X: A selector was called without passing the instance ID, which may impact the performance of the grid.', + 'To fix, call it with `apiRef`, for example `mySelector(apiRef)`, or pass the instance ID explicitly, for example `mySelector(state, apiRef.current.instanceId)`.', + ]); } } diff --git a/packages/x-data-grid/src/utils/warning.ts b/packages/x-data-grid/src/utils/warning.ts deleted file mode 100644 index f9eb314b2e76..000000000000 --- a/packages/x-data-grid/src/utils/warning.ts +++ /dev/null @@ -1,35 +0,0 @@ -export const buildWarning = ( - message: string | string[], - gravity: 'warning' | 'error' = 'warning', -) => { - let alreadyWarned = false; - - const cleanMessage = Array.isArray(message) ? message.join('\n') : message; - - return () => { - if (!alreadyWarned) { - alreadyWarned = true; - if (gravity === 'error') { - console.error(cleanMessage); - } else { - console.warn(cleanMessage); - } - } - }; -}; - -export const wrapWithWarningOnCall = ( - method: F, - message: string | string[], -) => { - if (process.env.NODE_ENV === 'production') { - return method; - } - - const warning = buildWarning(message); - - return (...args: any[]) => { - warning(); - return method(...args); - }; -}; diff --git a/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.tsx b/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.tsx index 4d4e418bea87..4a4974496318 100644 --- a/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.tsx +++ b/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.tsx @@ -21,7 +21,7 @@ import { PickerSelectionState, useNow, DEFAULT_DESKTOP_MODE_MEDIA_QUERY, - buildWarning, + warnOnce, useControlledValueWithTimezone, useViews, } from '@mui/x-date-pickers/internals'; @@ -77,11 +77,6 @@ const DateRangeCalendarMonthContainer = styled('div', { const weeksContainerHeight = (DAY_RANGE_SIZE + DAY_MARGIN * 2) * 6; -const warnInvalidCurrentMonthCalendarPosition = buildWarning([ - 'The `currentMonthCalendarPosition` prop must be an integer between `1` and the amount of calendars rendered.', - 'For example if you have 2 calendars rendered, it should be equal to either 1 or 2.', -]); - const DayCalendarForRange = styled(DayCalendar)(({ theme }) => ({ minWidth: 312, minHeight: weeksContainerHeight, @@ -506,7 +501,10 @@ const DateRangeCalendar = React.forwardRef(function DateRangeCalendar< const visibleMonths = React.useMemo(() => { if (process.env.NODE_ENV !== 'production') { if (currentMonthCalendarPosition > calendars || currentMonthCalendarPosition < 1) { - warnInvalidCurrentMonthCalendarPosition(); + warnOnce([ + 'MUI X: The `currentMonthCalendarPosition` prop must be an integer between `1` and the amount of calendars rendered.', + 'For example if you have 2 calendars rendered, it should be equal to either 1 or 2.', + ]); } } diff --git a/packages/x-date-pickers/src/AdapterDayjs/AdapterDayjs.ts b/packages/x-date-pickers/src/AdapterDayjs/AdapterDayjs.ts index 47221a9ac21b..0f76d85ad304 100644 --- a/packages/x-date-pickers/src/AdapterDayjs/AdapterDayjs.ts +++ b/packages/x-date-pickers/src/AdapterDayjs/AdapterDayjs.ts @@ -13,7 +13,7 @@ import { PickersTimezone, DateBuilderReturnType, } from '../models'; -import { buildWarning } from '../internals/utils/warning'; +import { warnOnce } from '../internals/utils/warning'; defaultDayjs.extend(localizedFormatPlugin); defaultDayjs.extend(weekOfYearPlugin); @@ -22,13 +22,6 @@ defaultDayjs.extend(advancedFormatPlugin); type Constructor = (...args: Parameters) => Dayjs; -const localeNotFoundWarning = buildWarning([ - 'Your locale has not been found.', - 'Either the locale key is not a supported one. Locales supported by dayjs are available here: https://github.com/iamkun/dayjs/tree/dev/src/locale', - "Or you forget to import the locale from 'dayjs/locale/{localeUsed}'", - 'fallback on English locale', -]); - const formatTokenMap: FieldFormatTokenMap = { // Year YY: 'year', @@ -255,7 +248,14 @@ export class AdapterDayjs implements MuiPickersAdapter { let localeObject = locales[locale]; if (localeObject === undefined) { - localeNotFoundWarning(); + if (process.env.NODE_ENV !== 'production') { + warnOnce([ + 'MUI X: Your locale has not been found.', + 'Either the locale key is not a supported one. Locales supported by dayjs are available here: https://github.com/iamkun/dayjs/tree/dev/src/locale.', + "Or you forget to import the locale from 'dayjs/locale/{localeUsed}'", + 'fallback on English locale.', + ]); + } localeObject = locales.en; } diff --git a/packages/x-date-pickers/src/internals/hooks/usePicker/usePicker.ts b/packages/x-date-pickers/src/internals/hooks/usePicker/usePicker.ts index 62f6ac8089da..b150b8fe3dee 100644 --- a/packages/x-date-pickers/src/internals/hooks/usePicker/usePicker.ts +++ b/packages/x-date-pickers/src/internals/hooks/usePicker/usePicker.ts @@ -3,16 +3,10 @@ import { usePickerValue } from './usePickerValue'; import { usePickerViews } from './usePickerViews'; import { usePickerLayoutProps } from './usePickerLayoutProps'; import { InferError } from '../useValidation'; -import { buildWarning } from '../../utils/warning'; +import { warnOnce } from '../../utils/warning'; import { FieldSection, PickerValidDate } from '../../../models'; import { DateOrTimeViewWithMeridiem } from '../../models'; -const warnRenderInputIsDefined = buildWarning([ - 'The `renderInput` prop has been removed in version 6.0 of the Date and Time Pickers.', - 'You can replace it with the `textField` component slot in most cases.', - 'For more information, please have a look at the migration guide (https://mui.com/x/migration/migration-pickers-v5/#input-renderer-required-in-v5).', -]); - export const usePicker = < TValue, TDate extends PickerValidDate, @@ -40,7 +34,11 @@ export const usePicker = < >): UsePickerResponse> => { if (process.env.NODE_ENV !== 'production') { if ((props as any).renderInput != null) { - warnRenderInputIsDefined(); + warnOnce([ + 'MUI X: The `renderInput` prop has been removed in version 6.0 of the Date and Time Pickers.', + 'You can replace it with the `textField` component slot in most cases.', + 'For more information, please have a look at the migration guide (https://mui.com/x/migration/migration-pickers-v5/#input-renderer-required-in-v5).', + ]); } } const pickerValueResponse = usePickerValue({ diff --git a/packages/x-date-pickers/src/internals/index.ts b/packages/x-date-pickers/src/internals/index.ts index d336ee07f1e0..bf9c07dd4c1f 100644 --- a/packages/x-date-pickers/src/internals/index.ts +++ b/packages/x-date-pickers/src/internals/index.ts @@ -151,7 +151,7 @@ export { validateDate } from './utils/validation/validateDate'; export { validateDateTime } from './utils/validation/validateDateTime'; export { validateTime } from './utils/validation/validateTime'; export { applyDefaultViewProps } from './utils/views'; -export { buildDeprecatedPropsWarning, buildWarning } from './utils/warning'; +export { warnOnce } from './utils/warning'; export { DayCalendar } from '../DateCalendar/DayCalendar'; export type { diff --git a/packages/x-date-pickers/src/internals/utils/warning.ts b/packages/x-date-pickers/src/internals/utils/warning.ts index 109a1abfc878..f0d34fc59f68 100644 --- a/packages/x-date-pickers/src/internals/utils/warning.ts +++ b/packages/x-date-pickers/src/internals/utils/warning.ts @@ -1,41 +1,25 @@ -export const buildDeprecatedPropsWarning = (message: string | string[]) => { - let alreadyWarned = false; +const warnedOnceCache = new Set(); +// TODO move to @mui/x-internals +// TODO eventually move to @base_ui/internals. Base UI, etc. too need this helper. +export function warnOnce(message: string | string[], gravity: 'warning' | 'error' = 'warning') { if (process.env.NODE_ENV === 'production') { - return () => {}; + return; } const cleanMessage = Array.isArray(message) ? message.join('\n') : message; - return (deprecatedProps: { [key: string]: any }) => { - const deprecatedKeys = Object.entries(deprecatedProps) - .filter(([, value]) => value !== undefined) - .map(([key]) => `- ${key}`); + if (!warnedOnceCache.has(cleanMessage)) { + warnedOnceCache.add(cleanMessage); - if (!alreadyWarned && deprecatedKeys.length > 0) { - alreadyWarned = true; - - console.warn([cleanMessage, 'deprecated props observed:', ...deprecatedKeys].join('\n')); + if (gravity === 'error') { + console.error(cleanMessage); + } else { + console.warn(cleanMessage); } - }; -}; - -export const buildWarning = ( - message: string | string[], - gravity: 'warning' | 'error' = 'warning', -) => { - let alreadyWarned = false; - - const cleanMessage = Array.isArray(message) ? message.join('\n') : message; + } +} - return () => { - if (!alreadyWarned) { - alreadyWarned = true; - if (gravity === 'error') { - console.error(cleanMessage); - } else { - console.warn(cleanMessage); - } - } - }; -}; +export function clearWarningsCache() { + warnedOnceCache.clear(); +} diff --git a/packages/x-tree-view-pro/src/RichTreeViewPro/RichTreeViewPro.tsx b/packages/x-tree-view-pro/src/RichTreeViewPro/RichTreeViewPro.tsx index 3eee6b03f115..bb318e951ef6 100644 --- a/packages/x-tree-view-pro/src/RichTreeViewPro/RichTreeViewPro.tsx +++ b/packages/x-tree-view-pro/src/RichTreeViewPro/RichTreeViewPro.tsx @@ -3,7 +3,7 @@ import composeClasses from '@mui/utils/composeClasses'; import { useLicenseVerifier, Watermark } from '@mui/x-license'; import { useSlotProps } from '@mui/base/utils'; import { TreeItem, TreeItemProps } from '@mui/x-tree-view/TreeItem'; -import { useTreeView, TreeViewProvider, buildWarning } from '@mui/x-tree-view/internals'; +import { useTreeView, TreeViewProvider, warnOnce } from '@mui/x-tree-view/internals'; import { styled, createUseThemeProps } from '../internals/zero-styled'; import { getRichTreeViewProUtilityClass } from './richTreeViewProClasses'; import { RichTreeViewProProps } from './RichTreeViewPro.types'; @@ -65,12 +65,6 @@ function WrappedTreeItem({ const releaseInfo = getReleaseInfo(); -const childrenWarning = buildWarning([ - 'MUI X: The `RichTreeViewPro` component does not support JSX children.', - 'If you want to add items, you need to use the `items` prop', - 'Check the documentation for more details: https://mui.com/x/react-tree-view/rich-tree-view/items/', -]); - /** * * Demos: @@ -91,7 +85,11 @@ const RichTreeViewPro = React.forwardRef(function RichTreeViewPro< if (process.env.NODE_ENV !== 'production') { if ((props as any).children != null) { - childrenWarning(); + warnOnce([ + 'MUI X: The `RichTreeViewPro` component does not support JSX children.', + 'If you want to add items, you need to use the `items` prop.', + 'Check the documentation for more details: https://mui.com/x/react-tree-view/rich-tree-view/items/.', + ]); } } diff --git a/packages/x-tree-view/src/RichTreeView/RichTreeView.tsx b/packages/x-tree-view/src/RichTreeView/RichTreeView.tsx index 753d3cde0668..0736c5efa810 100644 --- a/packages/x-tree-view/src/RichTreeView/RichTreeView.tsx +++ b/packages/x-tree-view/src/RichTreeView/RichTreeView.tsx @@ -9,7 +9,7 @@ import { useTreeView } from '../internals/useTreeView'; import { TreeViewProvider } from '../internals/TreeViewProvider'; import { RICH_TREE_VIEW_PLUGINS, RichTreeViewPluginSignatures } from './RichTreeView.plugins'; import { TreeItem, TreeItemProps } from '../TreeItem'; -import { buildWarning } from '../internals/utils/warning'; +import { warnOnce } from '../internals/utils/warning'; const useThemeProps = createUseThemeProps('MuiRichTreeView'); @@ -61,12 +61,6 @@ function WrappedTreeItem({ return {children}; } -const childrenWarning = buildWarning([ - 'MUI X: The `RichTreeView` component does not support JSX children.', - 'If you want to add items, you need to use the `items` prop', - 'Check the documentation for more details: https://mui.com/x/react-tree-view/rich-tree-view/items/', -]); - /** * * Demos: @@ -85,7 +79,11 @@ const RichTreeView = React.forwardRef(function RichTreeView< if (process.env.NODE_ENV !== 'production') { if ((props as any).children != null) { - childrenWarning(); + warnOnce([ + 'MUI X: The `RichTreeView` component does not support JSX children.', + 'If you want to add items, you need to use the `items` prop.', + 'Check the documentation for more details: https://mui.com/x/react-tree-view/rich-tree-view/items/.', + ]); } } diff --git a/packages/x-tree-view/src/SimpleTreeView/SimpleTreeView.tsx b/packages/x-tree-view/src/SimpleTreeView/SimpleTreeView.tsx index cac64b7acf05..2763dfc3e658 100644 --- a/packages/x-tree-view/src/SimpleTreeView/SimpleTreeView.tsx +++ b/packages/x-tree-view/src/SimpleTreeView/SimpleTreeView.tsx @@ -8,7 +8,7 @@ import { SimpleTreeViewProps } from './SimpleTreeView.types'; import { useTreeView } from '../internals/useTreeView'; import { TreeViewProvider } from '../internals/TreeViewProvider'; import { SIMPLE_TREE_VIEW_PLUGINS, SimpleTreeViewPluginSignatures } from './SimpleTreeView.plugins'; -import { buildWarning } from '../internals/utils/warning'; +import { warnOnce } from '../internals/utils/warning'; const useThemeProps = createUseThemeProps('MuiSimpleTreeView'); @@ -42,12 +42,6 @@ type SimpleTreeViewComponent = ( { - let alreadyWarned = false; +const warnedOnceCache = new Set(); + +// TODO move to @mui/x-internals +// TODO eventually move to @base_ui/internals. Base UI, etc. too need this helper. +export function warnOnce(message: string | string[], gravity: 'warning' | 'error' = 'warning') { + if (process.env.NODE_ENV === 'production') { + return; + } const cleanMessage = Array.isArray(message) ? message.join('\n') : message; - return () => { - if (!alreadyWarned) { - alreadyWarned = true; - if (gravity === 'error') { - console.error(cleanMessage); - } else { - console.warn(cleanMessage); - } + if (!warnedOnceCache.has(cleanMessage)) { + warnedOnceCache.add(cleanMessage); + + if (gravity === 'error') { + console.error(cleanMessage); + } else { + console.warn(cleanMessage); } - }; -}; + } +} + +export function clearWarningsCache() { + warnedOnceCache.clear(); +}