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

[Autocomplete] Grouping logic is broken #19109

Closed
2 tasks done
liangchunn opened this issue Jan 6, 2020 · 5 comments · Fixed by #19121
Closed
2 tasks done

[Autocomplete] Grouping logic is broken #19109

liangchunn opened this issue Jan 6, 2020 · 5 comments · Fixed by #19121
Labels
component: autocomplete This is the name of the generic UI component, not the React module!

Comments

@liangchunn
Copy link
Contributor

  • The issue is present in the latest release.
  • I have searched the issues of this repository and believe that this is not a duplicate.

Current Behavior 😯

Given a set of data and the groupBy functor:

const data = [
  {
    group: 1,
    value: "A",
    intrinsicProperty: "#"
  },
  {
    group: 2,
    value: "B",
    intrinsicProperty: "#"
  },
  {
    group: 2,
    value: "C",
    intrinsicProperty: "#"
  },
  {
    group: 1,
    value: "D",
    intrinsicProperty: "#"
  }
];


const groupBy = datum => datum.group

The logic as described here produces a grouping of:

[
  {
    "key": 1,
    "index": 0,
    "options": [
      {
        "group": 1,
        "value": "A"
      }
    ]
  },
  {
    "key": 2,
    "index": 1,
    "options": [
      {
        "group": 2,
        "value": "B"
      },
      {
        "group": 2,
        "value": "C"
      }
    ]
  },
  {
    "key": 1,
    "index": 3,
    "options": [
      {
        "group": 1,
        "value": "D"
      }
    ]
  }
]

Which either:

  • shows on the Autocomplete suggestions as
 1:
    A
 2:
    B
    C
 1:
    D
  • crashes the app with
Warning: Encountered two children with the same key, `1`. Keys should be unique so that components maintain their identity across updates.

Expected Behavior 🤔

I expect that the grouping would look something like this:

 1:
    A
    D
 2:
    B
    C

Steps to Reproduce 🕹

Steps:

  1. Go to https://codesandbox.io/s/withered-silence-jcrgd
  2. Click on the input box
  3. See error

Context 🔦

The logic linked in "Current Behavior" is flawed, particularly this part:

if (acc.length > 0 && acc[acc.length - 1].key === key) {
    acc[acc.length - 1].options.push(option);
} else {
    acc.push({
        key,
        index,
        options: [option],
    });
}

A more contrived example:

const data = [
    { group: 1, value: "A" },
    { group: 2, value: "B" },
    { group: 2, value: "C" },
    { group: 1, value: "D" },
    { group: 3, value: "E" },
    { group: 2, value: "F" },
    { group: 1, value: "G" }
]

const groupBy = datum => datum.group

Running the 'faulty' logic yields the result (which causes buggyness due to duplicate key):

[ { key: 1, index: 0, options: [ [Object] ] },
  { key: 2, index: 1, options: [ [Object], [Object] ] },
  { key: 1, index: 3, options: [ [Object] ] },
  { key: 3, index: 4, options: [ [Object] ] },
  { key: 2, index: 5, options: [ [Object] ] },
  { key: 1, index: 6, options: [ [Object] ] } ]

Replacing the logic with:

if (groupBy) {
  let index = 0
  const indexByKey = {}
  const result = []
  for (const option of filteredOptions) {
    const key = groupBy(option)
    if (indexByKey[key] === undefined) {
      indexByKey[key] = index
      result.push({
        key,
        index,
        options: []
      })
      index++
    }
    result[indexByKey[key]].options.push(option)
  }
  let counter = 0
  for (const option of result) {
    option.index = counter
    counter += option.options.length
  }
  groupedOptions = result
}

yields the correctly grouped options:

[ { key: 1, index: 0, options: [ [Object], [Object], [Object] ] },
  { key: 2, index: 3, options: [ [Object], [Object], [Object] ] },
  { key: 3, index: 6, options: [ [Object] ] } ]

Your Environment 🌎

Tech Version
Material-UI Core v4.8.3
Material-UI Lab v4.0.0-alpha.39
React v16.11.0
Browser Chrome
TypeScript v3.7.2
@liangchunn liangchunn changed the title [Autocomplete] groupBy base functor's logic is broken [Autocomplete] Grouping logic is broken Jan 6, 2020
@oliviertassinari
Copy link
Member

oliviertassinari commented Jan 6, 2020

I like the proposal. The order seems to be preserved and the performance impact looks OK.

The current logic only groups sequential options, but it should probably group them all. My only concern with your proposed solution is the usage of for (const option of, I'm not sure how it transpiles, maybe use forEach instead?

@liangchunn
Copy link
Contributor Author

Could be implemented in another way, I was just illustrating the desired result 😄

Looping the array with for would be doable as well, and the perf shouldn’t be impacted that much.

@Janpot
Copy link
Member

Janpot commented Jan 6, 2020

🙂 Also be careful with this code, it will break on:

const groupBy = () => 'toString'

I'd suggest to use

const indexByKey = Object.create(null)

or a Map

liangchunn added a commit to liangchunn/material-ui that referenced this issue Jan 7, 2020
liangchunn added a commit to liangchunn/material-ui that referenced this issue Jan 7, 2020
@oliviertassinari oliviertassinari added the component: autocomplete This is the name of the generic UI component, not the React module! label Jul 28, 2020
@jeunesseBurce
Copy link

@oliviertassinari I am encountering a same problem in grouping. Tried to sort it but what I need to grouped are data with nested children on it. And I wanted it to be grouped by its parent node. Would this be possible using AutoComplete?

data is like as follows:

[{
  name: "1"
  parent: null
      children: [
        {
          name: "1.1"
          parent: "1"
        },
        {
         name: "1.2"
         parent: "1"
       }
      ]
}]

with this kind of data it shows this error:
Material-UI: The options provided combined with the groupBymethod of Autocomplete returns duplicated headers. You can solve the issue by sorting the options with the output ofgroupBy.

Is there a way for me to have no duplicates and group them all by their specified parent?

@oliviertassinari
Copy link
Member

@jeunesseBurce Please ask support questions on StackOverflow

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
component: autocomplete This is the name of the generic UI component, not the React module!
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants