Converting GraphQL data for the CustomTreeData component of DevExtreme-Reactive

    It was necessary to display the data in the form of a tree, with the ability to edit different fields, delete / add lines, etc. In the process of searching for suitable components (I wanted to find under material-ui and react) I began to try devextreme-reactive . Newance, however, found that devextreme-reactive wants data for the tree as a flat array of objects, each of which contains the parent_id of the "parent". And GraphQL server gives me a tree in the form of nested objects with arrays of objects. I had to do something else from one thing — perhaps someone would come in handy. And maybe someone will say that I was not confused about the case and all this is done much easier.

    So, in response to the GraphQL query (there are tests, each has questions, for each survey there are several answer options and we want to get everything at once):

    query TestQuery {
      tests {
        id
        title
        questions {
          id
          title
          answers {
            id
            title
          }
        }
      }
    }
    

    We get a response from the server like:

    Spoiler header
    {
      "data": {
        "tests": [
          {
            "id": "test_1",
            "title": "Test 1",
            "questions": [
              {
                "id": "question_1",
                "title": "Question 1 (for t1)",
                "answers": [
                  {
                    "id": "answer_1",
                    "title": "Answer 1 (for q1)"
                  },
                  {
                    "id": "answer_2",
                    "title": "Answer 2 (for q1)"
                  }
                ]
              },
              {
                "id": "question_2",
                "title": "Question 2 (for t1)",
                "answers": [
                  {
                    "id": "answer_1_2",
                    "title": "Answer 1 (for q2)"
                  }
                ]
              }
            ]
          },
          {
            "id": "test_2",
            "title": "Test 2",
            "questions": [
              {
                "id": "question_1_2",
                "title": "Question 1 (for t2)",
                "answers": []
              }
            ]
          },
          {
            "id": "test_3",
            "title": "Test 3",
            "questions": []
          }
        ]
      }
    }
    


    For normalization we use normalizr :

    In the description of the scheme, through the processStrategy, we add pid properties to children with links to parents. By the way, the way of describing schemes has changed in the fresh normalizr, which is why examples with assignEntity, ArrayOf, define (there are a lot of them) are practically irrelevant.

    const answerSchema = new schema.Entity('answers',{}, {
            processStrategy: (entity, parent, key) => { return { ...entity, pid: parent.id} }
        }
    )
    const questionSchema = new schema.Entity('questions',{
        answers:[answerSchema]}, {
            processStrategy: (entity, parent, key) => { return { ...entity, pid: parent.id} }
        },
    )
    const testSchema = new schema.Entity('tests',{questions:[questionSchema]}, { 
            processStrategy: (entity, parent, key) => { return { ...entity, pid: 0 } }
        }
    )
    const nRes = normalize(result.data, {tests: [testSchema]})        
    

    We get this:

    Spoiler header
    {
      "entities": {
        "answers": {
          "answer_1": {
            "id": "answer_1",
            "title": "Answer 1 (for q1)",
            "__typename": "Answer",
            "pid": "question_1"
          },
          "answer_2": {
            "id": "answer_2",
            "title": "Answer 2 (for q1)",
            "__typename": "Answer",
            "pid": "question_1"
          },
          "answer_1_2": {
            "id": "answer_1_2",
            "title": "Answer 1 (for q2)",
            "__typename": "Answer",
            "pid": "question_2"
          }
        },
        "questions": {
          "question_1": {
            "id": "question_1",
            "title": "Question 1 (for t1)",
            "answers": [
              "answer_1",
              "answer_2"
            ],
            "__typename": "Question",
            "pid": "test_1"
          },
          "question_2": {
            "id": "question_2",
            "title": "Question 2 (for t1)",
            "answers": [
              "answer_1_2"
            ],
            "__typename": "Question",
            "pid": "test_1"
          },
          "question_1_2": {
            "id": "question_1_2",
            "title": "Question 1 (for t2)",
            "answers": [
            ],
            "__typename": "Question",
            "pid": "test_2"
          }
        },
        "tests": {
          "test_1": {
            "id": "test_1",
            "title": "Test 1",
            "questions": [
              "question_1",
              "question_2"
            ],
            "__typename": "Test",
            "pid": 0
          },
          "test_2": {
            "id": "test_2",
            "title": "Test 2",
            "questions": [
              "question_1_2"
            ],
            "__typename": "Test",
            "pid": 0
          },
          "test_3": {
            "id": "test_3",
            "title": "Test 3",
            "questions": [
            ],
            "__typename": "Test",
            "pid": 0
          }
        }
      },
      "result": {
        "tests": [
          "test_1",
          "test_2",
          "test_3"
        ]
      }
    }
    


    We are only interested in .entities

    const normalized = { entities: nRes.entities }
    

    By the way, in the process of driving into normalizr, after reading the issues, I found that I’m not the only one trying to use it for quite a purpose (probably just because it is almost the only such tool). Many people crave all sorts of features to get the result in the most customizable format. But the authors are flint.

    In view of the foregoing, the result of the work of normalizr will have to be straightened with the help of flat (we recursively expand to the required level of nesting):

    const flattened = flatten({ entities: nRes.entities }, { maxDepth: 3 })
    

    We get the following:

    Spoiler header
    {
      "entities.answers.answer_1": {
        "id": "answer_1",
        "title": "Answer 1 (for q1)",
        "__typename": "Answer",
        "pid": "question_1"
      },
      "entities.answers.answer_2": {
        "id": "answer_2",
        "title": "Answer 2 (for q1)",
        "__typename": "Answer",
        "pid": "question_1"
      },
      "entities.answers.answer_1_2": {
        "id": "answer_1_2",
        "title": "Answer 1 (for q2)",
        "__typename": "Answer",
        "pid": "question_2"
      },
      "entities.questions.question_1": {
        "id": "question_1",
        "title": "Question 1 (for t1)",
        "answers": [
          "answer_1",
          "answer_2"
        ],
        "__typename": "Question",
        "pid": "test_1"
      },
      "entities.questions.question_2": {
        "id": "question_2",
        "title": "Question 2 (for t1)",
        "answers": [
          "answer_1_2"
        ],
        "__typename": "Question",
        "pid": "test_1"
      },
      "entities.questions.question_1_2": {
        "id": "question_1_2",
        "title": "Question 1 (for t2)",
        "answers": [
        ],
        "__typename": "Question",
        "pid": "test_2"
      },
      "entities.tests.test_1": {
        "id": "test_1",
        "title": "Test 1",
        "questions": [
          "question_1",
          "question_2"
        ],
        "__typename": "Test",
        "pid": 0
      },
      "entities.tests.test_2": {
        "id": "test_2",
        "title": "Test 2",
        "questions": [
          "question_1_2"
        ],
        "__typename": "Test",
        "pid": 0
      },
      "entities.tests.test_3": {
        "id": "test_3",
        "title": "Test 3",
        "questions": [
        ],
        "__typename": "Test",
        "pid": 0
      }
    }
    


    Getting rid of the indices:

    Object.keys(flattened).forEach( (key)=> rows.push(flattened[key]) )
    

    We get:

    Spoiler header
    [
      {
        "id": "answer_1",
        "title": "Answer 1 (for q1)",
        "__typename": "Answer",
        "pid": "question_1"
      },
      {
        "id": "answer_2",
        "title": "Answer 2 (for q1)",
        "__typename": "Answer",
        "pid": "question_1"
      },
      {
        "id": "answer_1_2",
        "title": "Answer 1 (for q2)",
        "__typename": "Answer",
        "pid": "question_2"
      },
      {
        "id": "question_1",
        "title": "Question 1 (for t1)",
        "answers": [
          "answer_1",
          "answer_2"
        ],
        "__typename": "Question",
        "pid": "test_1"
      },
      {
        "id": "question_2",
        "title": "Question 2 (for t1)",
        "answers": [
          "answer_1_2"
        ],
        "__typename": "Question",
        "pid": "test_1"
      },
      {
        "id": "question_1_2",
        "title": "Question 1 (for t2)",
        "answers": [
        ],
        "__typename": "Question",
        "pid": "test_2"
      },
      {
        "id": "test_1",
        "title": "Test 1",
        "questions": [
          "question_1",
          "question_2"
        ],
        "__typename": "Test",
        "pid": 0
      },
      {
        "id": "test_2",
        "title": "Test 2",
        "questions": [
          "question_1_2"
        ],
        "__typename": "Test",
        "pid": 0
      },
      {
        "id": "test_3",
        "title": "Test 3",
        "questions": [
        ],
        "__typename": "Test",
        "pid": 0
      }
    ]
    


    It would be possible to clean the remaining arrays of questions, questions, answers here, but these are trifles - they do not affect the display. And __ typename are needed, so that later when editing you understand what we are dealing with.

    In the component, the result is processed as shown in their example :

    ...
    <CustomTreeData getChildRows={getChildRows} />
    ...
    const getChildRows = (currentRow, rootRows) => {
        const childRows = rootRows.filter(r => r.pid === (currentRow ? currentRow.id : 0));
        const res = childRows.length ? childRows : nullreturn res
    }
    ...
    

    It seems that an alternative to all of the above may be reading directly the contents of the GraphQL store (in the Apollo client) - everything should also be flat there. But, to be honest, I did not find how this can be done in a standard way, and I’m not very sure that the format in which data is stored there will not change in new versions.

    Also popular now: