mui / mui-x

MUI X: Build complex and data-rich applications using a growing list of advanced React components, like the Data Grid, Date and Time Pickers, Charts, and more!
https://mui.com/x/
4.05k stars 1.25k forks source link

[data grid] Autocomplete edit input cell #4813

Open stamahto opened 2 years ago

stamahto commented 2 years ago

Summary 💡

Should work like normal Autocomplete component. Unlike 'singleSelect', users can type in input and search options quickly.

Examples 🌈

image

Motivation 🔦

Autocomplete provides more freedom for users and also developers than select (singleSelect), like: freeSolo, getOptionLabel or multiple props.

Order ID 💳 (optional)

31956

alexfauquette commented 2 years ago

I see multiple points in this proposal:

stamahto commented 2 years ago

I see multiple points in this proposal:

Yes, you're right. Thank you 👍 The best way is not create column type 'autocomplete', but add functionality to existing ones (i.e. getOptionLabel can be used for any column type), just add multipleSelect.

What I haven't mentioned yet is that if I don't define valueOptions for column type: single/multiSelect, I would expect default options as grouped/unique data from the current column. This is very common in my practise, also mui-datatables do that for filter type dropdown image

alexfauquette commented 2 years ago

Yes, getOptionLabel could be used for both singleSelect and a multipleSelect columns. For other types I don't think so. You rarely expect to see a list of options when entering a date or a number

I'm not sure to fully understand your last point valueOptions is here to define a finite set of options the user can select. If you do not provide a valueOptions parameters, it is similar to using string column type. I assume your expectation is the API allows you to easily get the array of unique data present in the grid such as you can make them available in an autocomplete.

But that would concern only the filtering aspect

stamahto commented 2 years ago

I believe that in many apps, you always have some information according to which you want to group/filter data or just get the most data consistency (including case sensitivity) for something. Imagine, for example, a warehouse where individual items have a manufacturer, type, etc. image Currently, to get these values, I have to manually write for each column something like: const manufacturers: string[] = useMemo(() => uniq(data.map(m => m.manufacturer)), [data]);

I think this is a much better way to automatically display options than as an empty field, as long as the valueOptions are not defined in the column.

I really appreciate your time, thank you.

alexfauquette commented 2 years ago

Ok, I better understand the use case.

The technical challenge is to provide access throw the API to the list of unique values. Once done, this list of values could be reused wherever we want: a new custom renderEditCell or a filterInput, ...

adamrneary commented 2 years ago

@alexfauquette we have recently converted to "Pro" and have been tinkering with this problem. I think until this receives the necessary upvotes to get a canonical solution, perhaps a working example in the docs would get us there. I've managed to get us this far:

function AutocompleteEditCell<
  T extends { value: string; label: string },
  Multiple extends boolean = false,
  DisableClearable extends boolean = false,
  FreeSolo extends boolean = false
>({
  id,
  value,
  field,
  options,
  disableClearable,
  multiple,
  freeSolo,
}: GridRenderEditCellParams & {
  options: UseAutocompleteProps<
    T,
    Multiple,
    DisableClearable,
    FreeSolo
  >['options'];
  disableClearable: DisableClearable;
  multiple: Multiple;
  freeSolo: FreeSolo;
}) {
  const apiRef = useGridApiContext();
  const handleValueChange = (
    _: any,
    newValue: AutocompleteValue<T, Multiple, DisableClearable, FreeSolo>
  ) => {
    apiRef.current.setEditCellValue({
      id,
      field,
      // @ts-expect-error i can't figure out how to use AutocompleteValue
      value: typeof newValue === 'string' ? value : newValue?.value || '',
    });
  };

  return (
    <Autocomplete<T, Multiple, DisableClearable, FreeSolo>
      fullWidth
      disableClearable={disableClearable}
      multiple={multiple}
      options={options}
      freeSolo={freeSolo}
      // @ts-expect-error i can't figure out how to use AutocompleteValue
      value={options.find((o) => o.value === value)?.label || ''}
      onChange={handleValueChange}
      renderInput={(params) => <TextField {...params} />}
    />
  );
}

and this is referenced thusly:

{
    field: 'naicsCode',
    headerName: 'Industry',
    width: 250,
    editable: true,
    valueFormatter: ({ value }) => NAICS_OBJECT[value] ?? ''
    renderEditCell: (params) => (
      <AutocompleteEditCell
        {...params}
        options={NAICS_OPTIONS}
        freeSolo={false}
        multiple={false}
        disableClearable
      />
    ),  
},

First, if you see anything obvious we could be doing to clear up those two @ts-expect-errors I'd love your eyes on those! But more broadly for this task, it seems like something to this effect could go into the docs to explain how to do it manually? Similar to https://mui.com/x/react-data-grid/editing/#usage-with-mui-x-date-pickers perhaps?

stamahto commented 2 years ago

Thanks @adamrneary for example. It's been a while so I have written solution too. Here is my example:

import { Autocomplete, TextField } from "@mui/material";
import { GridRenderEditCellParams, useGridApiContext } from "@mui/x-data-grid-pro";
import { useCallback } from "react";

interface AutocompleteEditInputCellProps {
    params: GridRenderEditCellParams,
    options: any[] | undefined,
    freeSolo?: boolean,
    multiple?: boolean,
    getOptionLabel?: (option: any) => string
}

export function AutocompleteEditInputCell(props: AutocompleteEditInputCellProps) {
    const { params, options, freeSolo, getOptionLabel, multiple } = props;
    const apiRef = useGridApiContext();

    const handleChange = useCallback((event: React.SyntheticEvent<Element, Event>, newValue: any) => {
        event.stopPropagation();
        apiRef.current.setEditCellValue({ id: params.id, field: params.field, value: newValue });
    }, [params.id, params.field]);

    const getValue = useCallback(() => {
        if (params.value)
            return params.value;

        if (multiple)
            return [];

        return null;
    }, [params.value, multiple]);

    return (
        <Autocomplete
            value={getValue()}
            onChange={handleChange}
            onInputChange={(event, value) => (freeSolo && !multiple && event) && handleChange(event, value)}
            fullWidth
            multiple={multiple}
            options={options ?? []}
            freeSolo={freeSolo}
            autoHighlight
            getOptionLabel={getOptionLabel}
            renderInput={(inputParams) => <TextField {...inputParams} error={params.error} />}
        />
    );
}

In function handleChange needs to be event.stopPropagation(); to succesfully handle selection from open list values by hitting enter, otherwise it would close edited row without value selection.

ghost commented 2 years ago

Yes, I am using Autocomplete in 4 columns in a grid, since it is far better for large drop down lists since it allows user to search from the list by entering first few characters. Though I need single option select only. My Upvote.

On the other note:- I have done custom component setup as suggested in docs and similar to what @stamhato has done, and it has been working like charm till "@mui/x-data-grid-pro": "5.16.0",

When I upgrade my data-grid-pro version to 5.17.1 it starts malfunctioning, the moment the focus goes in the autocomplete cell, my processRowUpdate get triggered and the row got saved. To save from this bug, I have reverted back to 5.16.0.

I am not sure if I should be logging a new issue for this or if this comment of mine will catch the attention of the material-ui team. Ref Order ID 💳 (optional) 28914

alexfauquette commented 2 years ago

@adamrneary You should open another issue with a minimal reproduction of your customization that bugs in v5.17.1

Otherwise, this bug/regression will be lost into other comments asking for feature

oliviertassinari commented 2 years ago

I agree with the UX recommendation made by the author of this issue. I think that it could be great to replace most uses of Select for an Autocomplete, I assume that it would yield a better UX for end-users.

IMHO, the only question is: Should we always use the Autocomplete over the Select? Airtable said yes. Notion said no: use a Select if there are always fewer than 5 options.

For example with the singleSelect column type edit mode: :100: to replace the default Select to have an Autocomplete, like done by these:

stamahto commented 2 years ago

I agree with the UX recommendation made by the author of this issue. I think that it could be great to replace most uses of Select for an Autocomplete, I assume that it would yield a better UX for end-users.

IMHO, the only question is: Should we always use the Autocomplete over the Select? Airtable said yes. Notion said no: use a Select if there are always fewer than 5 options.

Well, I don't know the answer either, but here are some of my thoughts:

AXLShorts commented 1 year ago

I know its a lot to ask but can anyone provide me a snippet of a working autocomplete functionality inside a cell in the datagrid. I am unable to implement it

stamahto commented 1 year ago

I know its a lot to ask but can anyone provide me a snippet of a working autocomplete functionality inside a cell in the datagrid. I am unable to implement it

My code few comments up here is updated and works pretty well: https://github.com/mui/mui-x/issues/4813#issuecomment-1190009699 The usage is: renderEditCell: params => <AutocompleteEditInputCell params={params} options={["A", "B"]} />

AXLShorts commented 1 year ago

I got it to work like this:

import { useState } from "react";
import { DataGrid } from "@mui/x-data-grid";

const UserPage = () => {
  const [users, setUsers] = useState([
    { id: 1, name: "John Doe", email: "john@example.com", role: "basic" },
    { id: 2, name: "Jane Smith", email: "jane@example.com", role: "admin" },
    // Add more user objects as needed
  ]);

  const handleRoleChange = (event, id) => {
    const newUsers = [...users];
    const userIndex = newUsers.findIndex((user) => user.id === id);
    if (userIndex !== -1) {
      newUsers[userIndex].role = event.target.value;
      setUsers(newUsers);
      console.log(newUsers);
    }
  };

  const columns = [
    { field: "name", headerName: "Name", width: 150 },
    { field: "email", headerName: "Email", width: 200 },
    {
      field: "role",
      headerName: "Role",
      width: 120,
      type: "singleSelect",
      valueOptions: ["basic", "admin"],
      editable: true,
    },
  ];

  return (
    <div style={{ height: 400, width: "100%" }}>
      <DataGrid rows={users} columns={columns} />
    </div>
  );
};

export default UserPage;

Now it works how you would expect. The cells becomes autocomplete type elements when you start editing them. But now I'm having slight trouble in handling the change. Any idea on how to track change here?

hcurbelo-hepyco commented 1 year ago

Have a problem when used an Autocomplete inside DataGrid MUI

Screenshot 2023-06-11 at 19 58 57
AXLShorts commented 1 year ago

I gave a code snippet above. That works

hcurbelo-hepyco commented 1 year ago

@AXLShorts Thk, this issue is solved

arvindkannan commented 1 year ago

Have a problem when used an Autocomplete inside DataGrid MUI

Screenshot 2023-06-11 at 19 58 57

@hcurbelo-hepyco I also have same issue where in system displays [object Object] using autocomplete inside mui data grid edit mode

Update:

Was able to manage it via renderCell method to display value from object.

aeftimia commented 10 months ago

For what it's worth, setting renderCell and renderEditCell to the autocomplete box with editable seems to work fine for the community version for me.

sureshvv commented 8 months ago

I got it to work like this:

import { useState } from "react";
import { DataGrid } from "@mui/x-data-grid";

const UserPage = () => {
  const [users, setUsers] = useState([
    { id: 1, name: "John Doe", email: "john@example.com", role: "basic" },
    { id: 2, name: "Jane Smith", email: "jane@example.com", role: "admin" },
    // Add more user objects as needed
  ]);

  const handleRoleChange = (event, id) => {
    const newUsers = [...users];
    const userIndex = newUsers.findIndex((user) => user.id === id);
    if (userIndex !== -1) {
      newUsers[userIndex].role = event.target.value;
      setUsers(newUsers);
      console.log(newUsers);
    }
  };

  const columns = [
    { field: "name", headerName: "Name", width: 150 },
    { field: "email", headerName: "Email", width: 200 },
    {
      field: "role",
      headerName: "Role",
      width: 120,
      type: "singleSelect",
      valueOptions: ["basic", "admin"],
      editable: true,
    },
  ];

  return (
    <div style={{ height: 400, width: "100%" }}>
      <DataGrid rows={users} columns={columns} />
    </div>
  );
};

export default UserPage;

Now it works how you would expect. The cells becomesg autocomplete type elements when you start editing them. But now I'm having slight trouble in handling the change. Any idea on how to track change here?

who is calling handleRoleChange and how does it know to di it?