Guide on integrating an element into a different element in a Vue 3 Tree Viewer

In my current setup, I've implemented a TreeView component that holds a tree. Each tree entry includes Children with their own unique label, perm, and further children.

Take a look at an example of the tree:

App.vue

let tree = ref({
  label: 'org',
  perm: {
    POST: "auth",
    GET: "null"
  },
  children: [
    {
      label: '*',
      perm: {
        POST: "auth",
        GET: "null"
      },
      children: [{
        label: 'repo',
        perm: {
          POST: "null",
          GET: "null"
        },
        children: []
      }]
    },
    {
      label: 'test',
      perm: {
        POST: "auth",
        GET: "auth"
      },
      children: []
    }
  ]
})

To display this in a simple Tree View, I'm using the following setup:

TreeView.vue

 <div @click="toggleHideShow()"> 
     {{ label }}
 </div>
 <div class="child" v-if="hideShow">
     <TreeView v-for="child in children" :label="child.label" :perm="child.perm" :children="child.children" />
 </div>

My goal is to be able to dynamically add children through this TreeView. Is there a way for me to access the exact Children object upon clicking?

Currently, I attempted to pass attributes through the toggleHideShow method:

 <div @click="toggleHideShow(label, perm, children)"> 

After that, I create a new Children object, iterate through the tree, and compare each one to find the matching object.

const toggleHideShow = (label: string, perm: Perm, children?: Array<Children>)=> {
    hideShow.value = !hideShow.value
    const newChild: Children = {
        label: label,
        perm: perm,
        children: children
    }

    //do for loop here
}

The issue arises from my tree variable being declared in App.vue, which I then pass into my TreeView component.

<TreeView :label="tree.label" :perm="tree.perm" :children="tree.children" />

Here are my three questions:

1: How can I access the tree variable within my TreeView component? Will modifying it also alter the tree variable inside App.vue, therefore updating the TreeView dynamically?

2: Would it be more efficient to create a method within App.vue that takes in the newly created newChild variable as a parameter and adds it to the tree from there? If so, how would I implement this method in my TreeView component?

3: Are there other methods for me to identify the specific Children object within the tree that was clicked on? And how can I retrieve it from the tree variable considering it's not an array?

Answer №1

When dealing with recursion, every change made to a child has ripple effects at the parent level. For instance, if you are 2 levels deep, updating the child triggers a chain reaction where the parent and grandparent also need updates.

This pattern persists even when navigating 10 levels deep.
The common assumption is that performance would suffer under this system, but in reality, Vue efficiently handles change detection and rendering.

The real challenge lies in debugging. When errors surface at a certain depth, pinpointing the issue becomes a daunting task.

A simpler alternative is recommended:

  • Place all items in a flat array and assign unique identifiers
  • Rather than nesting items within children, refer to them using their ids only

With this approach, updating an item does not necessitate notifications to its lineage.

An item editor can easily modify the currently selected item without concerns about cascading changes or broadcasting updates across levels.

Check out the working demo: https://codesandbox.io/s/editable-tree-bo6msx?file=/src/App.vue

To keep things organized, the tree logic is abstracted into a store that offers methods for adding/removing/editing tree items.

This method's flexibility allows for potential modification to support multiple parents for an item if needed (mainly tweaking the removeChild method to search for all parents).

Answer №2

It's a good idea to avoid changing prop values passed down to child components. Instead, emit events from the children up to the root node (App.vue) and make changes to the tree structure there. This process is not too complex because arrays and objects are passed by reference, allowing you to emit the actual children array of the current TreeView node. App.vue can then push new items to that array, updating the appropriate section within tree without the need for searching.

In the snippet below, I maintain the hide/show toggle on the {{ label }} and include a new clickable span for adding a child node when clicked. This span emits the current children array. All TreeView nodes capture this emit and pass it up until it reaches App.vue, where the new child is actually added.

TreeView.vue

<template>
  <li>
    <div>
      <span @click="hideShow = !hideShow">{{ label }}</span>
      <span @click="$emit('addChild', children)">(add child)</span>
    </div>

    <div v-if="isOpen" class="child">
      <ul>
        <TreeView
          v-for="(child, i) in children"
          :key="i"
          :label="child.label"
          :perm="child.perm"
          :children="child.children"
          @add-child="$emit('addChild', $event)"
        />
      </ul>
    </div>
  </li>

App.vue

<template>
  <ul>
    <TreeView
      :label="tree.label"
      :perm="tree.perm"
      :children="tree.children"
      @add-child="addChild($event)"
    />
  </ul>
</template>
const addChild = item => {
  item.push({
    label: 'new item',
    perm: {},
    children: []
  });
};

By following this approach, App is able to update the same array in tree even though it is receiving an array via emit.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Error: JSON parsing encountered an unexpected token at character 182704

While working on a Vue Application, an error occurred when running npm run build in the GitHub Actions workflow. The specific error message was: SyntaxError: Unexpected token in JSON at position 182704 at JSON.parse (<anonymous>) at /home/ru ...

Angular HTTP post is failing on the first attempt but succeeds on the second try

Just started working on my first Angular exercise and encountered an issue where I am receiving an undefined value on the first attempt from an HTTP post request. However, on the second try, I am getting the proper response in Edge and Firefox. Thank you f ...

When you call Subscribe(), you will receive the output "complete: () => void; 'complete' is defined in this context. What exactly does this signify?

Currently, I am following a tutorial and after meticulously checking and rechecking, I can confidently say that my code matches exactly with the one the professor is using. The tutorial involves creating a simple Single Page Application in Angular using Ty ...

The Unusual Behavior of Typescript Partial Interfaces

While reviewing the code in a repository I am currently working on, I stumbled upon something that seemed completely incorrect. Here is a snippet of what caught my attention. interface Car { make: string model: string } type SomeType = Partial<Car& ...

Will the async pipe activate onPush change detection in Angular?

I have searched various sources for the question above, but I am finding conflicting answers. For example, on Angular University's website, it is mentioned that change detection is triggered when the async pipe receives a new observable value. However ...

"Capture the selected option from a dropdown menu and display it on the console: A step-by-step

Is there a way to store the selected value from a dropdown in a variable and then display it on the console? HTML <select class="form-control box" id="title" required> <option *ngIf="nationality_flag">{{nationality}}</option> &l ...

data not corresponding to interface

I am encountering an issue that says Type '({ name: string; href: string; icon: IconDefinition; } | { name: string; href: string; icon: IconDefinition; childs: { name: string; href: string; icon: IconDefinition; }[]; })[]' is missing the followin ...

Vite HMR causes Vue component to exceed the maximum number of recursive updates

After making changes to a nested component in Vue and saving it, I noticed that the Vite HMR kept reloading the component, resulting in a warning from Vue: Maximum recursive updates exceeded... Check out the online demo on stackblitz. Make a change in Chi ...

Error in ThreeJS: Unable to execute material.customProgramCacheKey

I encountered an issue TypeError: material.customProgramCacheKey is not a function The error pops up when I invoke the function this.animate(). However, no error occurs when the URL is empty. Where could this error be originating from since I don't ...

Tips for denoting unnecessary non-null assertions in Typescript

Incorporated this wrapper (source) into the project I'm currently working on: export function expectToBeDefined<T>( arg: T, ): asserts arg is Exclude<T, undefined> { expect(arg).toBeDefined(); } The objective is to eliminate the usage ...

Do these exports serve the same purpose?

Can someone help me understand why one export works while the other doesn't? I've tried to figure it out on my own but I'm stuck. Functioning Example const dataStore = new Vapi({ baseURL: 'http://domain.test/api', state: ...

Is it possible to set up Vite/Rollup in a way that only triggers vue-tsc for dependencies that are utilized by the entrypoints?

Currently, I am in the process of upgrading my Vue 2 project to Vue 3 and have decided to migrate from the Vue CLI to Vite since it is now end of life. The upgrade has resulted in numerous breaking changes, requiring refactoring of most files in the /src f ...

Tips for streamlining code using switch statements in vue.js

Is there a more efficient way to simplify this switch statement for handling 5 different cases? Can I streamline the process of updating the completion status based on each step? data() { return { stepOneIsCompleted: false, ...

Determining if an item is empty, undefined, or null in Angular: a guide

I received a .json file structured as data [0 ... n]. Each position in the data array contains an object with various attributes, such as: {photo1, photo2, photo3 ... photoN} For a visual representation of how the json file is formatted, you can check ...

"Unlocking the treasure trove: Extracting a single item from my Firebase real-time database using

Searching for the user's article in my Realtime database to display information. This is my Ionic script page Below are the imports I have: I am able to retrieve the user's ID, but I'm facing difficulty in fetching this real-time data fro ...

Taking control of a keyboard shortcut in Vue

I need to detect the combination of pressing shift+8 on my keyboard. v-on:keyup.shift.56.prevent="dosomething" The method is being called successfully, but it also displays the standard symbol *. Is there a way to prevent this output from showing? ...

Step-by-step guide on integrating async and await functionality into Vuetify rules using an ajax call

I'm currently using Vuetify form validation to validate an input field, and I'm exploring the possibility of making an ajax get call await so that I can utilize the result in a rule. However, my attempts at this approach have not been successful! ...

Tips for deleting on a numeric cell within ag-grid?

While exploring the functionality of AG-Grid with the example provided at this link [, I am currently experimenting with the numeric editor feature. I found this example on the official AG-Grid website [https://www.ag-grid.com/javascript-grid-cell-editor/ ...

Issue: Child Pages not found in Nuxt RoutingDescription: When navigating

Currently working on a Nuxt application that utilizes the WordPress REST API to fetch content. While my other routes are functioning correctly, I am facing challenges with nested pages. The structure I have implemented in my Nuxt app is as follows: pages ...

Semantic-ui-react cannot be located by Docker

I am a beginner when it comes to docker and I'm looking to create a React app, specifically using TypeScript, inside a docker container. In order to do this, I need to incorporate semantic-ui-react into my project. I followed the instructions provide ...