0

I am trying to achieve something a bit complex.

I am working on a grouping feature where I have to get the items containing the key checked: true then I need to put them together within the key/array named questionGroup in the first item in the array with checked: true and then deleting the other items that were grouped.

See the structure of the data:

const dummyQuestions = [
  {
    id: 1,
    checked: false,
    question: {...},
    questionGroup: []
  },
  {
    id: 2,
    checked: true,
    question: {...},
    questionGroup: []
  },
  {
    id: 3,
    checked: false,
    question: {...},
    questionGroup: []
  },
  {
    id: 4,
    checked: true,
    question: {...},
    questionGroup: []
  },
  {
    id: 5,
    checked: true,
    question: {...},
    questionGroup: []
  }
];

So lets say you clicked on a certain button which fires a grouping function, as you noticed index 1, 3 and 4 have checked: true, so this must happen:

const dummyQuestions = [
  {
    id: 1,
    checked: false,
    question: {...},
    questionGroup: []
  },
  {
    id: 2,
    checked: true,
    question: {...},
    questionGroup: [
      {
        id: 2,
        checked: true,
        question: {...},
        questionGroup: []
      },
      {
        id: 4,
        checked: true,
        question: {...},
        questionGroup: []
      },
      {
        id: 5,
        checked: true,
        question: {...},
        questionGroup: []
      }
    ]
  },
  {
    id: 3,
    checked: false,
    question: {...},
    questionGroup: []
  }
];

That's how the data must be after grouping it.

Do you get the idea?

I created a reproducible demo => https://codesandbox.io/s/ts-react-grouping-odxs1?file=/src/App.tsx

EDIT

This is the relevant code:

const handleGroupQuestions = (): void => {
    const questionAddedCopy: VariableConfig[] = [...questionAdded];

    // Only the questions with checked === true will be grouped.
    const filteredCopy: VariableConfig[] = questionAddedCopy.filter(
      (q: VariableConfig) => q.checked === true
    );

    const grouped: VariableConfig[] = [
      ...questionAdded.filter((q: VariableConfig) => filteredCopy.includes(q))
    ];

    for (let i = 0; i < grouped.length; i++) {
      // Here I compare the ids and I put them together into the key questionGroup
      if (filteredCopy[i].id === grouped[i].id) {
        // I had to set any instead of VariableConfig type because otherwise TS throws an error
        addQuestion((q: any) => {
          const questionsCopy = [...q];

          const mergedQuestions: VariableConfig[] = [
            ...questionsCopy,
            { ...grouped[i], questionGroup: grouped }
          ];

          // With this `unique` function what I am trying to achieve (which I am not) is
          // to have a unique ID per every question/item in the array.
          // I need to add the questions to questionGroup to the highest item containing check = true
          // I need to delete from the main array, the items going inside questionGroup.
          const unique = [
            ...mergedQuestions.filter((c) => c.id !== questionsCopy[i].id)
          ];

          return unique;
        });
        break;
      }
    }
  };

And to explain more in deep and to answer your questions.

We see that the first item in the array with checked: true is the item with the id 2, so that will be the place where I have to stored the other items with checked: true because that is the highest item in the array, then I need to delete from the array the items that were grouped into questionGroup.

The id 2 in this case is repeated and that is a requirement, because one id is for the parent and the other one within questionGroup is its own child, get it? Like if it is storing itself into questionGroup.

This is just a grouping functionality where I am saving some steps. Imagine every item contains a checkbox, so the items that you want to group, you have to check them first, then click the GROUP THEM button, the checked items disappear and then get together into the highest checked index, keeping its id and storing itself as well.

Take a look at the arrays I posted. There you can see what is the exact output I need.

Reacting
  • 5,543
  • 7
  • 35
  • 86
  • 1
    What have you tried so far? It'll be easier to help you if you post an attempt at the algorithm. – y2bd Feb 21 '21 at 03:09
  • 1
    There's an algorithm attempt in the codesandbox demo link... although any pertinent code should also be in the text of the question as mentioned in the "Help others reproduce the problem" section of [ask]. I still don't understand the desired outcome, though... I guess we want to remove `checked:true` items from the array and append them to the `questionGroup` property of one of the *other* items in the array, but I can't figure out which one, or if it's just one, or what. – jcalz Feb 21 '21 at 03:16
  • 1
    I'm a bit afraid to ask for more info here because I think this question has been deleted and re-asked multiple times after I asked for more info before. I promise I'm just trying to help. – jcalz Feb 21 '21 at 03:17
  • I can make no sense of what this output grouping has to do with the input and the `checked` set of `1`, `3`, and `4`... even if I make the possible but not clear guess that the original list had sequential `id`s of `1`, `2`, `3`, `4`, and `5`. Why are `1`, `2`, and `3` at the root? And why are `2`, `4`, and `5` nested inside `2`? And do you really want `2` nested inside itself? Please [edit] to include the relevant code of your attempt here. (The whole app is not necessary, only the function which does this data conversion.) – Scott Sauyet Feb 21 '21 at 05:48
  • a basic idea would be to filter your array by only returning true when items are check:false. If check:true, push the item inside some other array. The last thing being to insert back the first item which had check:true while iterating at the proper position (and appending as children the items in "other array"). I am not answering because it is UNCLEAR why id 2 repeats in expected result – grodzi Feb 21 '21 at 10:01
  • @jcalz see my edit. And actually this is the first time I post this question. Thanks for you help! – Reacting Feb 22 '21 at 00:40
  • @grodzi the item with id `2` repeats because it is storing itself into its `questionGroup` key/array. Why? Because it is `checked: true`. Just imagine all of these items contain a checkbox, so check the items you want to group, then click on a button that fires the grouping function, then delete from the core array the checked items except the highest checked item because it will be the place where the other checked items are going to be stored within the `questionGroup` array. – Reacting Feb 22 '21 at 00:54
  • Hey @ScottSauyet see my edit please. I added some code and also more explanation. – Reacting Feb 22 '21 at 00:55
  • Does [this](https://tsplay.dev/gWovjm) meet your needs? If so I'll write up an answer. If not, please let me know how and/or explain in more detail how you intend to deal with the element duplication since I'm not sure I really understand it. – jcalz Feb 22 '21 at 02:32
  • the "main" checkbox id 2 has a question group array of length 3, while the checkbox id 2 being a child has an empty array. It means they are not the same checkbox even though their id is 2. Is it intended ? – grodzi Feb 22 '21 at 06:17
  • I'd suggest that you don't really want a "repeated" item, but that the parent should be a different type of thing entirely, like [this](https://tsplay.dev/Ympq6W). Unless you are going to have a whole tree of questions, you don't need bare questions to contain groups. – jcalz Feb 22 '21 at 16:18

1 Answers1

2

Here is one technique:

// helper function
const addChecked = (q, qs) =>
  ({...q, questionGroup: qs .filter (p => p .checked)})

// main function
const nestChecked = (qs, found = false) =>
  qs .flatMap (q => q .checked ? found ? [] : (found = true, [addChecked (q, qs)]) : [q])

// sample data
const dummyQuestions = [{id: 1, checked: false, question: 'foo', questionGroup: []}, {id: 2, checked: true, question: 'bar', questionGroup: []}, {id: 3, checked: false, question: 'baz', questionGroup: []}, {id: 4, checked: true, question: 'qux', questionGroup: []}, {id: 5, checked: true, question: 'corge', questionGroup: []}]

// demo
console .log  (JSON .stringify (nestChecked (dummyQuestions), null, 4))
.as-console-wrapper {max-height: 100% !important; top: 0}

We have a simple helper function that groups all the checked elements of the input into (a copy of) a specific question.

Our main function iterates over the questions with .flatMap, which allows us to do a filter and map in a single traversal. We maintain a flag, found which tells us if we've already handled the checked elements. For each question, if it's not checked, we simply include it, by returning it wrapped in an array, as [q] from the flatMap callback. If it is checked, we evaluate the found flag. If that is set to true, then we return and empty array. If it's not, we return the result of calling our helper function above, again wrapped in an array. (These array wrappers are not specifically needed for this problem; but I like to return a consistent type in the flatMap callback.)

We could easily inline that helper function, as it's only called once. In fact that's how I originally wrote it. But it feels cleaner to me separated.

It's not clear to me if there is some iterative process which will then further nest these. If so, you might have to do something more sophisticated in the helper function dealing wit already-populated questionGroup fields. But that would probably be for a different question.

Finally, we also might want to avoid that default parameter. There are sometimes good reasons to want to do so. I leave this change as an exercise for the reader. :-)

Scott Sauyet
  • 49,207
  • 4
  • 49
  • 103
  • I can't tell whether OP needs to modify the existing data or make new data, and if the identity of any of these arrays needs to be maintained somehow. But this looks like a good solution to me. – jcalz Feb 22 '21 at 16:03
  • 1
    @jcalz: Well, if the OP is looking to mutate the original data, there won't be any help from me. I'm not a barbarian. ;-) – Scott Sauyet Feb 22 '21 at 16:06
  • I am not trying to mutate the data. All good, Scott. It does just what I need. Thanks, guys. – Reacting Feb 22 '21 at 22:54