Skip to content

Commit

Permalink
[core] Warn when defaultValue changes (#19070)
Browse files Browse the repository at this point in the history
  • Loading branch information
m4theushw authored and oliviertassinari committed Jan 9, 2020
1 parent dd7b772 commit 5757d97
Show file tree
Hide file tree
Showing 15 changed files with 238 additions and 221 deletions.
2 changes: 1 addition & 1 deletion packages/material-ui-lab/src/Autocomplete/Autocomplete.js
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ const Autocomplete = React.forwardRef(function Autocomplete(props, ref) {
setAnchorEl,
inputValue,
groupedOptions,
} = useAutocomplete(props);
} = useAutocomplete({ ...props, componentName: 'Autocomplete' });

let startAdornment;

Expand Down
38 changes: 10 additions & 28 deletions packages/material-ui-lab/src/TreeView/TreeView.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import clsx from 'clsx';
import PropTypes from 'prop-types';
import TreeViewContext from './TreeViewContext';
import { withStyles } from '@material-ui/core/styles';
import { useControlled } from '@material-ui/core/utils';

export const styles = {
/* Styles applied to the root element. */
Expand Down Expand Up @@ -46,28 +47,13 @@ const TreeView = React.forwardRef(function TreeView(props, ref) {
const nodeMap = React.useRef({});
const firstCharMap = React.useRef({});

const { current: isControlled } = React.useRef(expandedProp !== undefined);
const [expandedState, setExpandedState] = React.useState(defaultExpanded);
const expanded = (isControlled ? expandedProp : expandedState) || defaultExpandedDefault;

if (process.env.NODE_ENV !== 'production') {
// eslint-disable-next-line react-hooks/rules-of-hooks
React.useEffect(() => {
if (isControlled !== (expandedProp != null)) {
console.error(
[
`Material-UI: A component is changing ${
isControlled ? 'a ' : 'an un'
}controlled TreeView to be ${isControlled ? 'un' : ''}controlled.`,
'Elements should not switch from uncontrolled to controlled (or vice versa).',
'Decide between using a controlled or uncontrolled TreeView ' +
'element for the lifetime of the component.',
'More info: https://fb.me/react-controlled-components',
].join('\n'),
);
}
}, [expandedProp, isControlled]);
}
const [expandedState, setExpandedState] = useControlled({
controlled: expandedProp,
default: defaultExpanded,
name: 'TreeView',
});

const expanded = expandedState || defaultExpandedDefault;

const prevChildIds = React.useRef([]);
React.useEffect(() => {
Expand Down Expand Up @@ -198,9 +184,7 @@ const TreeView = React.forwardRef(function TreeView(props, ref) {
onNodeToggle(event, newExpanded);
}

if (!isControlled) {
setExpandedState(newExpanded);
}
setExpandedState(newExpanded);
};

const expandAllSiblings = (event, id) => {
Expand All @@ -216,9 +200,7 @@ const TreeView = React.forwardRef(function TreeView(props, ref) {
}
const newExpanded = [...expanded, ...diff];

if (!isControlled) {
setExpandedState(newExpanded);
}
setExpandedState(newExpanded);

if (onNodeToggle) {
onNodeToggle(event, newExpanded);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ export interface UseAutocompleteProps {
* If `true`, clear all values when the user presses escape and the popup is closed.
*/
clearOnEscape?: boolean;
/**
* The component name that is using this hook. Used for warnings.
*/
componentName?: string;
/**
* If `true`, the popup will ignore the blur event if the input if filled.
* You can inspect the popup markup with your browser tools.
Expand Down
39 changes: 12 additions & 27 deletions packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable no-constant-condition */
import React from 'react';
import PropTypes from 'prop-types';
import { setRef, useEventCallback } from '@material-ui/core/utils';
import { setRef, useEventCallback, useControlled } from '@material-ui/core/utils';

// https://stackoverflow.com/questions/990904/remove-accents-diacritics-in-a-string-in-javascript
// Give up on IE 11 support for this feature
Expand Down Expand Up @@ -108,6 +108,7 @@ export default function useAutocomplete(props) {
open: openProp,
options = [],
value: valueProp,
componentName = 'useAutocomplete',
} = props;

const [defaultId, setDefaultId] = React.useState();
Expand Down Expand Up @@ -186,30 +187,11 @@ export default function useAutocomplete(props) {
}
}

const { current: isControlled } = React.useRef(valueProp !== undefined);
const [valueState, setValue] = React.useState(() => {
return !isControlled ? defaultValue || (multiple ? [] : null) : null;
const [value, setValue] = useControlled({
controlled: valueProp,
default: defaultValue || (multiple ? [] : null),
name: componentName,
});
const value = isControlled ? valueProp : valueState;

if (process.env.NODE_ENV !== 'production') {
// eslint-disable-next-line react-hooks/rules-of-hooks
React.useEffect(() => {
if (isControlled !== (valueProp !== undefined)) {
console.error(
[
`Material-UI: A component is changing ${
isControlled ? 'a ' : 'an un'
}controlled useAutocomplete to be ${isControlled ? 'un' : ''}controlled.`,
'Elements should not switch from uncontrolled to controlled (or vice versa).',
'Decide between using a controlled or uncontrolled useAutocomplete ' +
'element for the lifetime of the component.',
'More info: https://fb.me/react-controlled-components',
].join('\n'),
);
}
}, [valueProp, isControlled]);
}

const { current: isInputValueControlled } = React.useRef(inputValueProp != null);
const [inputValueState, setInputValue] = React.useState('');
Expand Down Expand Up @@ -444,9 +426,8 @@ export default function useAutocomplete(props) {
if (onChange) {
onChange(event, newValue);
}
if (!isControlled) {
setValue(newValue);
}

setValue(newValue);
};

const selectNewValue = (event, newValue) => {
Expand Down Expand Up @@ -930,6 +911,10 @@ useAutocomplete.propTypes = {
* If `true`, clear all values when the user presses escape and the popup is closed.
*/
clearOnEscape: PropTypes.bool,
/**
* The component name that is using this hook. Used for warnings.
*/
componentName: PropTypes.string,
/**
* If `true`, the popup will ignore the blur event if the input if filled.
* You can inspect the popup markup with your browser tools.
Expand Down
34 changes: 8 additions & 26 deletions packages/material-ui/src/ExpansionPanel/ExpansionPanel.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import Collapse from '../Collapse';
import Paper from '../Paper';
import withStyles from '../styles/withStyles';
import ExpansionPanelContext from './ExpansionPanelContext';
import useControlled from '../utils/useControlled';

export const styles = theme => {
const transition = {
Expand Down Expand Up @@ -94,40 +95,21 @@ const ExpansionPanel = React.forwardRef(function ExpansionPanel(props, ref) {
...other
} = props;

const { current: isControlled } = React.useRef(expandedProp != null);
const [expandedState, setExpandedState] = React.useState(defaultExpanded);
const expanded = isControlled ? expandedProp : expandedState;

if (process.env.NODE_ENV !== 'production') {
// eslint-disable-next-line react-hooks/rules-of-hooks
React.useEffect(() => {
if (isControlled !== (expandedProp != null)) {
console.error(
[
`Material-UI: A component is changing ${
isControlled ? 'a ' : 'an un'
}controlled ExpansionPanel to be ${isControlled ? 'un' : ''}controlled.`,
'Elements should not switch from uncontrolled to controlled (or vice versa).',
'Decide between using a controlled or uncontrolled ExpansionPanel ' +
'element for the lifetime of the component.',
'More info: https://fb.me/react-controlled-components',
].join('\n'),
);
}
}, [expandedProp, isControlled]);
}
const [expanded, setExpandedState] = useControlled({
controlled: expandedProp,
default: defaultExpanded,
name: 'ExpansionPanel',
});

const handleChange = React.useCallback(
event => {
if (!isControlled) {
setExpandedState(!expanded);
}
setExpandedState(!expanded);

if (onChange) {
onChange(event, !expanded);
}
},
[expanded, isControlled, onChange],
[expanded, onChange, setExpandedState],
);

const [summary, ...children] = React.Children.toArray(childrenProp);
Expand Down
32 changes: 7 additions & 25 deletions packages/material-ui/src/RadioGroup/RadioGroup.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,18 @@ import React from 'react';
import PropTypes from 'prop-types';
import FormGroup from '../FormGroup';
import useForkRef from '../utils/useForkRef';
import useControlled from '../utils/useControlled';
import RadioGroupContext from './RadioGroupContext';

const RadioGroup = React.forwardRef(function RadioGroup(props, ref) {
const { actions, children, name, value: valueProp, onChange, ...other } = props;
const rootRef = React.useRef(null);

const { current: isControlled } = React.useRef(valueProp != null);
const [valueState, setValue] = React.useState(props.defaultValue);
const value = isControlled ? valueProp : valueState;

if (process.env.NODE_ENV !== 'production') {
// eslint-disable-next-line react-hooks/rules-of-hooks
React.useEffect(() => {
if (isControlled !== (valueProp != null)) {
console.error(
[
`Material-UI: A component is changing ${
isControlled ? 'a ' : 'an un'
}controlled RadioGroup to be ${isControlled ? 'un' : ''}controlled.`,
'Elements should not switch from uncontrolled to controlled (or vice versa).',
'Decide between using a controlled or uncontrolled RadioGroup ' +
'element for the lifetime of the component.',
'More info: https://fb.me/react-controlled-components',
].join('\n'),
);
}
}, [valueProp, isControlled]);
}
const [value, setValue] = useControlled({
controlled: valueProp,
default: props.defaultValue,
name: 'RadioGroup',
});

React.useImperativeHandle(
actions,
Expand All @@ -52,9 +36,7 @@ const RadioGroup = React.forwardRef(function RadioGroup(props, ref) {
const handleRef = useForkRef(ref, rootRef);

const handleChange = event => {
if (!isControlled) {
setValue(event.target.value);
}
setValue(event.target.value);

if (onChange) {
onChange(event, event.target.value);
Expand Down
32 changes: 7 additions & 25 deletions packages/material-ui/src/Select/SelectInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { refType } from '@material-ui/utils';
import Menu from '../Menu/Menu';
import { isFilled } from '../InputBase/utils';
import useForkRef from '../utils/useForkRef';
import useControlled from '../utils/useControlled';

function areEqualValues(a, b) {
if (typeof b === 'object' && b !== null) {
Expand Down Expand Up @@ -57,28 +58,11 @@ const SelectInput = React.forwardRef(function SelectInput(props, ref) {
...other
} = props;

const { current: isControlled } = React.useRef(valueProp != null);
const [valueState, setValueState] = React.useState(defaultValue);
const value = isControlled ? valueProp : valueState;

if (process.env.NODE_ENV !== 'production') {
// eslint-disable-next-line react-hooks/rules-of-hooks
React.useEffect(() => {
if (isControlled !== (valueProp != null)) {
console.error(
[
`Material-UI: A component is changing ${
isControlled ? 'a ' : 'an un'
}controlled Select to be ${isControlled ? 'un' : ''}controlled.`,
'Elements should not switch from uncontrolled to controlled (or vice versa).',
'Decide between using a controlled or uncontrolled Select ' +
'element for the lifetime of the component.',
'More info: https://fb.me/react-controlled-components',
].join('\n'),
);
}
}, [valueProp, isControlled]);
}
const [value, setValue] = useControlled({
controlled: valueProp,
default: defaultValue,
name: 'SelectInput',
});

const inputRef = React.useRef(null);
const [displayNode, setDisplayNode] = React.useState(null);
Expand Down Expand Up @@ -151,9 +135,7 @@ const SelectInput = React.forwardRef(function SelectInput(props, ref) {
newValue = child.props.value;
}

if (!isControlled) {
setValueState(newValue);
}
setValue(newValue);

if (onChange) {
event.persist();
Expand Down
Loading

0 comments on commit 5757d97

Please sign in to comment.