0
votes

I'm using Material UI's Autocomplete component in conjunction with their TextField component. Everything's working as expected with the exception of one issue. The TextField is not rendering its input value on mount. I checked that it's receiving the value, it just won't show for some reason.

The functionality I'm going for is to have the form inputs hold their values even when unmounted/mounted - so long as the root App component stays mounted. The state for the form lives at the top level of the app in Context. The form has the search field I mentioned, an MUI Select component, and submit button. I'm doing the same thing with the Select component and that's working fine, just not the Autocomplete/TextField. Is this issue related to Autocomplete specifically?

Context.js:

import React, { createContext, useState } from 'react';

const initialState = {
  autocompleteOptions: [],
  cohortInput: 13,
  searchInput: '',
};

const Context = createContext([{}, () => {}]);

export const ContextProvider = ({ children }) => {
  const [state, setState] = useState(initialState);
  return (
    <Context.Provider value={[state, setState]}>
      {children}
    </Context.Provider>
  );
}

export default Context;

SearchField.js:

import React, { useContext } from 'react';
import Autocomplete from '@material-ui/lab/Autocomplete';
import CloseIcon from '@material-ui/icons/Close';

import Context from '../../../Context';
import TextFieldWithAdornment from './TextFieldWithAdornment';

const SearchField = () => {
  const [context, setContext] = useContext(Context); // eslint-disable-line
  const { autocompleteOptions } = context;

  return (
    <Autocomplete
      closeIcon={<CloseIcon />}
      forcePopupIcon={false}
      freeSolo={!!autocompleteOptions}
      getOptionLabel={option => option}
      options={autocompleteOptions}
      renderInput={params => <TextFieldWithAdornment {...params} />}
    />
  );
};

export default SearchField;

TextFieldWithAdornment.js:

import React, { useContext } from 'react';
import SearchIcon from '@material-ui/icons/SearchTwoTone';

import Context from '../../../Context';
import { findMatches } from './helpers';
import { StyledInputAdornment, StyledTextField } from './styles';

const TextFieldWithAdornment = ({ InputProps, ...restProps }) => {
  const [context, setContext] = useContext(Context);
  const { searchInput } = context;

  const handleChange = ({ target: { value } }) => {
    const searchInput = value;
    const autocompleteOptions = value.length ? findMatches(value) : [];
    setContext({ ...context, autocompleteOptions, searchInput });
  };

  const handleBlur = () => {
    setContext({ ...context, autocompleteOptions: [] });
  };

  const startAdornment = (
    <StyledInputAdornment position="start">
      <SearchIcon />
    </StyledInputAdornment>
  );

  return (
    <StyledTextField
      InputProps={{ ...InputProps, startAdornment }}
      onBlur={handleBlur}
      onChange={handleChange}
      value={searchInput}
      {...restProps}
    />
  );
};

export default TextFieldWithAdornment;

CohortSelect.js:
(this is working)

import React, { useContext } from 'react';

import Context from '../../../Context';
import {
  CohortSelectWrapper,
  StyledLabel,
  StyledMenuItem,
  StyledSelect,
} from './styles';

const CohortSelect = () => {
  const [context, setContext] = useContext(Context);
  const { cohortInput } = context;

  const handleChange = ({ target: { value } }) => {
    setContext({ ...context, cohortInput: value });
  };

  return (
    <CohortSelectWrapper>
      <StyledLabel>cohort</StyledLabel>
      <StyledSelect onChange={handleChange} value={cohortInput}>
        <StyledMenuItem value={11}>11</StyledMenuItem>
        <StyledMenuItem value={12}>12</StyledMenuItem>
        <StyledMenuItem value={13}>13</StyledMenuItem>
        <StyledMenuItem value={14}>14</StyledMenuItem>
      </StyledSelect>
    </CohortSelectWrapper>
  );
};

export default CohortSelect;
1

1 Answers

0
votes

I just figured this out. I had the TextField as the controlled form component being passed the onChange & value props. I needed the Autocomplete to be controlled instead, making use of the 'onInputChange' and 'inputValue' props.

SearchField.js:

import React, { useContext } from 'react';
import Autocomplete from '@material-ui/lab/Autocomplete';
import CloseIcon from '@material-ui/icons/Close';

import Context from '../../../Context';
import TextFieldWithAdornment from './TextFieldWithAdornment';
import { findMatches } from './helpers';

const SearchField = () => {
  const [context, setContext] = useContext(Context);
  const { autocompleteOptions, searchInput } = context;

  const handleChange = event => {
    if (event) {
      const searchInput = event.target.value;
      const autocompleteOptions =
        event.target.value.length ? findMatches(event.target.value) : [];
      setContext({ ...context, autocompleteOptions, searchInput });
    }
  };

  const handleClear = () => {
    setContext({ ...context, autocompleteOptions: [] });
  };

  return (
    <Autocomplete
      closeIcon={<CloseIcon />}
      freeSolo={!!autocompleteOptions}
      getOptionLabel={option => option}
      inputValue={searchInput}
      onClose={handleClear}
      onInputChange={handleChange}
      options={autocompleteOptions}
      renderInput={params => <TextFieldWithAdornment {...params} />}
    />
  );
};

export default SearchField;

TextFieldWithAdornment.js:

import React, { useContext } from 'react';
import SearchIcon from '@material-ui/icons/SearchTwoTone';

import Context from '../../../Context';
import { StyledInputAdornment, StyledTextField } from './styles';

const TextFieldWithAdornment = ({ InputProps, ...restProps }) => {
  const [context, setContext] = useContext(Context);

  const handleBlur = () => {
    setContext({ ...context, autocompleteOptions: [] });
  };

  const startAdornment = (
    <StyledInputAdornment position="start">
      <SearchIcon />
    </StyledInputAdornment>
  );

  return (
    <StyledTextField
      InputProps={{ ...InputProps, startAdornment }}
      onBlur={handleBlur}
      {...restProps}
    />
  );
};

export default TextFieldWithAdornment;