reactjsmaterial-uiformikformik-material-ui

How to get value reusable Autocomplete component using Formik with Material-UI


I have a reusable Autocomplete component, and I need to get the selected object, but I'm not able to access this information outside the component.

See that on the component I can access the information.

But on the form I can't access this information.

How can I access the selected object on the form?

See the code in Sandbox!

I thank you for your help!

Autocomplete:

import * as React from "react";
import { FieldProps, getIn } from "formik";
import { TextField, CircularProgress } from "@material-ui/core";
import MuiAutocomplete, {
  createFilterOptions
} from "@material-ui/lab/Autocomplete";

const NewAutocomplete: React.FC<
  FieldProps & {
    label?: string,
    options: Array<{ label: string, value: number }>
  }
> = ({ textFieldProps, field, form, label, options, isLoading, ...props }) => {
  const filterOptions = createFilterOptions({
    matchFrom: "start",
    limit: 500
  });
  const errorText =
    getIn(form.touched, field.name) && getIn(form.errors, field.name);

  const valueInit = [
    {
      value: 0,
      label: ""
    }
  ];

  return (
    <MuiAutocomplete
      {...props}
      {...field}
      filterOptions={filterOptions}
      options={[...valueInit, ...options]}
      getOptionLabel={(option) => (option ? option.label : "")}
      getOptionSelected={(option, value) => option.value === value?.value}
      loading={isLoading}
      value={field.value}
      onChange={(e, value) => {
        form.setFieldValue(field.name, value);
        //console.log(value);
      }}
      renderInput={(props) => (
        <>
          <TextField
            {...props}
            {...textFieldProps}
            label={label}
            helperText={errorText?.value || errorText}
            error={!!errorText}
            InputProps={{
              ...props.InputProps,
              endAdornment: (
                <React.Fragment>
                  {isLoading ? (
                    <CircularProgress color="primary" size={20} />
                  ) : null}
                  {props.InputProps.endAdornment}
                </React.Fragment>
              )
            }}
          />
        </>
      )}
    />
  );
};

export default NewAutocomplete;

Form:

import { Grid } from "@material-ui/core";
import { Field, Form, Formik } from "formik";
import Autocomplete from "./component/Autocomplete";

const listFilms = [
  { label: "The Shawshank Redemption", value: 1 },
  { label: "The Godfather", value: 2 },
  { label: "The Godfather: Part II", value: 3 },
  { label: "The Dark Knight", value: 4 }
];

const initialValues = {
  film: {
    label: "",
    value: 0
  }
};

export default function App() {
  return (
    <div className="App">
      <Formik initialValues={initialValues}>
        {function Render({ errors, touched, isSubmitting, setFieldValue }) {
          return (
            <Form id="form">
              <Grid container direction="row">
                <Grid item xs={12}>
                  <Field
                    name="film"
                    component={Autocomplete}
                    label="Film"
                    options={listFilms}
                    onChange={(e, value) => {
                      console.log(value);
                    }}
                    textFieldProps={{
                      fullWidth: true,
                      margin: "dense",
                      variant: "outlined",
                      autoFocus: true
                    }}
                  />
                </Grid>
              </Grid>
            </Form>
          );
        }}
      </Formik>
    </div>
  );
}

Solution

  • I managed to solve passing the value via props: setFieldValue

    Autocomplete:

    import * as React from "react";
    import { FieldProps, getIn } from "formik";
    import { TextField, CircularProgress } from "@material-ui/core";
    import MuiAutocomplete, {
      createFilterOptions
    } from "@material-ui/lab/Autocomplete";
    
    const NewAutocomplete: React.FC<
      FieldProps & {
        label?: string,
        options: Array<{ label: string, value: number }>
      }
    > = ({
      textFieldProps,
      field,
      form,
      label,
      options,
      isLoading,
      setFieldValue,
      ...props
    }) => {
      const filterOptions = createFilterOptions({
        matchFrom: "start",
        limit: 500
      });
      const errorText =
        getIn(form.touched, field.name) && getIn(form.errors, field.name);
    
      const valueInit = [
        {
          value: 0,
          label: ""
        }
      ];
    
      return (
        <MuiAutocomplete
          {...props}
          {...field}
          filterOptions={filterOptions}
          options={[...valueInit, ...options]}
          getOptionLabel={(option) => (option ? option.label : "")}
          getOptionSelected={(option, value) => option.value === value?.value}
          loading={isLoading}
          value={field.value}
          onChange={(e, value) => {
            form.setFieldValue(field.name, value);
            if (setFieldValue) {
              setFieldValue(value);
            }
          }}
          renderInput={(props) => (
            <>
              <TextField
                {...props}
                {...textFieldProps}
                label={label}
                helperText={errorText?.value || errorText}
                error={!!errorText}
                InputProps={{
                  ...props.InputProps,
                  endAdornment: (
                    <React.Fragment>
                      {isLoading ? (
                        <CircularProgress color="primary" size={20} />
                      ) : null}
                      {props.InputProps.endAdornment}
                    </React.Fragment>
                  )
                }}
              />
            </>
          )}
        />
      );
    };
    
    export default NewAutocomplete;
    

    Form:

    import { Grid } from "@material-ui/core";
    import { Field, Form, Formik } from "formik";
    import { useState } from "react";
    import Autocomplete from "./component/Autocomplete";
    
    const listFilms = [
      { label: "The Shawshank Redemption", value: 1 },
      { label: "The Godfather", value: 2 },
      { label: "The Godfather: Part II", value: 3 },
      { label: "The Dark Knight", value: 4 }
    ];
    
    const initialValues = {
      film: {
        label: "",
        value: 0
      }
    };
    
    export default function App() {
      const [autocompleteValue, setAutocompleteValue] = useState(null);
    
      console.log(autocompleteValue);
    
      return (
        <div className="App">
          <Formik initialValues={initialValues}>
            {function Render({ formik, values, setFieldValue }) {
              return (
                <Form id="form">
                  <Grid container direction="row">
                    <Grid item xs={12}>
                      <Field
                        name="film"
                        component={Autocomplete}
                        label="Film"
                        options={listFilms}
                        textFieldProps={{
                          fullWidth: true,
                          margin: "dense",
                          variant: "outlined",
                          autoFocus: true
                        }}
                        setFieldValue={setAutocompleteValue}
                      />
                    </Grid>
                  </Grid>
                </Form>
              );
            }}
          </Formik>
        </div>
      );
    }
    

    Console log:

    {label: "The Godfather", value: 2}