Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Switching between controlled and uncontrolled input #478

Closed
Alex-Mason-1982 opened this issue Jun 21, 2018 · 8 comments
Closed

Switching between controlled and uncontrolled input #478

Alex-Mason-1982 opened this issue Jun 21, 2018 · 8 comments

Comments

@Alex-Mason-1982
Copy link

Alex-Mason-1982 commented Jun 21, 2018

I have set up an autocomplete with downshift and it works perfectly... except it gives me the following warning whenever i select an option.

Warning: A component is changing a controlled input of type text to be uncontrolled. Input elements should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component

As far as i can tell (by looking at what i set the value to before and after the selection) I am not changing the input to undefined or null (what most commonly causes this issue).

My downshift version is 1.31.16
My node version is 9.2.1
My npm version is 5.10.0
My material ui version is 1.2.3

My code is as follows

import React from 'react';
import PropTypes from 'prop-types';
import Downshift from 'downshift';
import TextField from '@material-ui/core/TextField';
import Paper from '@material-ui/core/Paper';
import Avatar from '@material-ui/core/Avatar';
import MenuItem from '@material-ui/core/MenuItem';
import ListItemText from '@material-ui/core/ListItemText';

export class Autocomplete extends React.PureComponent {
  static propTypes = {
    items: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
    onSelect: PropTypes.func.isRequired,
    onChange: PropTypes.func.isRequired,
    value: PropTypes.string,
    inputDetails: PropTypes.shape({}).isRequired,
    disconect: PropTypes.bool,
  };

  static defaultProps = {
    value: '',
    disconect: false,
  }

  getSuggestions = (items, inputValue, disconect) => {
    if(disconect){return []}
    return items.filter(suggestion => {
      const keep = !inputValue ||
                    (suggestion.primary || '').toLowerCase().match(inputValue.toLowerCase()) ||
                    (suggestion.secondary && suggestion.secondary.toLowerCase().match(inputValue.toLowerCase()));

      return keep;
    });
  }

  render() {
    const { onSelect, onChange, value, items, inputDetails, disconect } = this.props;
    return (
      <Downshift
        style={{position: 'relative'}}
        onChange={(selection, functions) => onSelect(selection, functions)}
        onStateChange={({inputValue, type}) => {
          if(type === '__autocomplete_change_input__' || type === '__autocomplete_click_item__'){
            onChange((inputValue || ''));
          }
        }}
        itemToString={(item) => { return item ? item.primary : ''}}
        render={({
          getInputProps,
          getItemProps,
          getLabelProps,
          isOpen,
          inputValue,
          highlightedIndex,
          selectedItem,
          clearSelection,
        }) => (
          <div>
            <TextField
              InputProps={{
                ...getInputProps(),
                placeholder: inputDetails.placeholder,
                name: inputDetails.name,
                id: inputDetails.id,
                value,
              }}
              label={inputDetails.label}
              fullWidth={inputDetails.fullWidth}
            />
            {isOpen &&
              <Paper style={{
                position: 'absolute',
                zIndex: 1,
              }}>
                {
                  this.getSuggestions(items, inputValue, disconect)
                  .map((item, index) => (
                    <MenuItem
                      {...getItemProps({
                        item: item,
                      })}
                      key={item._id}
                      style={{ height: 50 }}
                    >
                      {inputDetails.avitar &&
                        <Avatar>{item.primary[0]}</Avatar>
                      }
                      <ListItemText
                        primary={item.primary}
                        secondary={item.secondary}
                      />
                    </MenuItem>
                  ))
                }
              </Paper>
            }
          </div>
        )}
      />
     );
   }
}

export default (Autocomplete);

Reproducing Bug
To reproduce i type into the autofill field created and select an option from the suggestions that come up.

when a suggestion is selected it creates a warning that I am changing from a controlled to uncontrolled input.

@kentcdodds
Copy link
Member

Hi, could you please reproduce your issue in codesandbox? You can start with this one: http://kcd.im/ds-example

It's just really hard to debug your issue with a wall of unformatted code 😅

@kentcdodds
Copy link
Member

Without spending too much time on it, I think that this should be fixed with:

<TextField
              InputProps={getInputProps({
                placeholder: inputDetails.placeholder,
                name: inputDetails.name,
                id: inputDetails.id,
                value,
              })}
              label={inputDetails.label}
              fullWidth={inputDetails.fullWidth}
            />

This way downshift can compose things together properly.

@Alex-Mason-1982
Copy link
Author

Sorry about the wall of text it was formatted when i put it in i swear. the problem persists in my code after making your change but when i created the sandbox it did not give the error. Think it might be a version issue (sandbox version is higher than mine)? I don't know ill keep digging.

Autocomplete example

@kentcdodds
Copy link
Member

Go ahead and keep digging. If you find a bug, feel free to comment here.

@eriklharper
Copy link

I would think based on your example that you should control the inputValue prop directly on the Downshift component like this:

<Downshift inputValue={this.props.value} ... />

I'm running into this same issue even though I'm controlling it like that ☝️ but maybe I'm doing it wrong. Its hard to find a full-fledged "controlled" Downshift example anywhere and the docs don't fully explain how to control everything with a concrete example. Once I figure it out I'll try to post it up so others can know how to do it.

@eriklharper
Copy link

I figured it out. There were states (mostly initially) where I was passing null or undefined values for selectedItem and inputValue. I changed it to pass "" in the cases where a value is null or undefined which has resolved the console error. Might be good to update the docs to point this out more clearly.

@partyka1
Copy link

I got this error when I had:

<Downshift itemToString={(item) => (item ? item.value : "")}>

but my item didn't have value key. Switch to item.name(as my data had name key) helped

@paulgrieselhuber
Copy link

I was experiencing this issue as well. Solved it by setting value on the input:

<input
  {...getInputProps({
    type: "search",
    placeholder: "Search",
    id: "search",
    name: "search",
    className: loading ? "loading" : "",
    value: props.value,
    onChange: e => {
      e.persist();
      onChange(e, hits, refine);
    }
  })}
/>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants