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

Encountering an issue when attempting to save an excel file in Angular 8, receiving an error message that states "

When working with angular 8, I encountered an issue while trying to save an excel file. The error message displayed was as follows: ERROR TypeError: Failed to execute 'createObjectURL' on 'URL': Overload resolution failed. at Functi ...

What is the best way to provide JSON data in Angular?

I am working on an Angular 4 application that is using Webpack, and I am currently facing a challenge with serving a JSON file. I have two main questions regarding this: When the JSON file is static, I am struggling to configure Webpack to handle it the ...

How can a Vue.js toggle button dynamically change based on the combined state of two checkboxes?

My current project involves implementing a toggle feature that allows users to enable a day and then select between AM or PM using checkboxes. One issue I'm facing is how to de-toggle the button if a user unchecks both AM and PM options. Check out t ...

Challenges with incorporating asynchronously fetched data in component operations

I have encountered an issue where the data retrieved from a server in a service is available in the component's template but not in the actual code. This seems strange to me. I made the call in the ngOnInit method of my main AppComponent ngOnInit() { ...

JavaScript function to add or subtract

I need to include additional inputs for "+ and -" 60mm and 120mm in my function. How can I achieve this without turning all of them into separate functions? <template> <card class="card"> <h2>Measurement</h2> <form&g ...

Unveiling the Ultimate Method to Package Angular 2 Application using SystemJS and SystemJS-Builder

I'm currently in the process of developing an application and I am faced with a challenge of optimizing the performance of Angular 2 by improving the loading speed of all the scripts. However, I have encountered an error that is hindering my progress: ...

Mapping arrays from objects using Next.js and Typescript

I am trying to create a mapping for the object below using { ...product.images[0] }, { ...product.type[0] }, and { ...product.productPackages[0] } in my code snippet. This is the object I need to map: export const customImage = [ { status: false, ...

The upcoming developer manages to execute the program successfully, however, it continues to load indefinitely

Executing the command yarn dev consistently runs successfully in my VS Code terminal: $ yarn dev yarn run v1.22.19 warning ..\..\..\..\package.json: No license field $ next dev ready - started server on 0.0.0.0:3000, url: http://localho ...

"Seeking advice on how to nest a service provider within another one in AngularJS 2. Any

I am faced with a product and cart list scenario. Below is the function I have created to iterate through the cart list and retrieve the specific product using its ID. getCartList(){ this.cart = CART; this.cart.forEach((cart: Cart) => ...

The process of incorporating user properties into the output of a Service Bus topic from a Javascript Azure Function

I'm currently developing a TypeScript Azure Function that utilizes an Azure Service Bus topic as its output. Although I am able to send messages successfully, I have encountered difficulties in setting custom metadata properties for the message. In m ...

Using TypeScript, you can pass an object property name as a function argument while ensuring the type is

How can I define a type in a function argument that corresponds to one of the object properties with the same type? For instance, if I have an object: type Article = { name: string; quantity: number; priceNet: number; priceGross: number; }; and I ...

How to Perform a Redirect to a Named Route in Vue.js?

Currently, I am in the process of working on a Vue.js project and have set up a route with the following configuration: { path: '/Cart', component: Cart, props: (route) => ({ payment_id: route.query.payment_id, payment_request_id: ...

How can I only accept one of two specific function signatures in Typescript and reject any others?

Looking for an answer from the community on Typescript, require either of two function signatures In my code, I am handling a callback where I need to either pass an error or leave the first argument as undefined and pass data as the second argument. To ...

Storing data in GridFS with MongoDB from an express buffer

Currently, I am attempting to save an Image file that I'm sending as multipart in my MongoDB utilizing GridFS. My approach involves using multer with the memoryStorage option. let upload = multer({ storage: multer.memoryStorage() }).single('ima ...

Encountering an issue with importing typeDefs in Apollo Server (Typescript) from an external file

I decided to move the typeDefs into a separate file written in typescript and then compiled into another file. Upon doing so, I encountered the following error: node:internal/errors:490 ErrorCaptureStackTrace(err); ^ Error [ERR_MODULE_NOT_FOUND]: Una ...

Six Material-UI TextFields sharing a single label

Is there a way to create 6 MUI TextField components for entering 6 numbers separated by dots, all enclosed within one common label 'Code Number' inside a single FormControl? The issue here is that the label currently appears only in the first tex ...

Tips for enhancing the FastifyRequest interface with a new property without erasing existing information in a declaration file

What is the method to integrate a property into an interface via declarations, while avoiding full object overwriting? declare module 'fastify' { interface FastifyRequest { user: User; } } //auth.ts ... const user = jwt.verify( ...

Using Kendo's Angular Grid to link data sources

I'm currently facing a minor issue with the Kendo Grid for Angular. My attempt to bind data after fetching is resulting in the following error: ERROR TypeError: Cannot read properties of undefined (reading 'api') This indicates that this. ...

What is the conventional method for incorporating style elements into Vue.js components?

Imagine I have a Vue component with data retrieved from an external API, containing information about various books: data: () => ({ books: [ {name: 'The Voyage of the Beagle', author: 'Charles Darwin'}, {name: &ap ...

What is the best way to combine async/await with a custom Promise class implementation?

I've created a unique Promise class. How can I incorporate it with async/await? type Resolve<T> = (x: T | PromiseLike<T>) => void type Reject = (reason?: any) => void class CustomizedPromise<T> extends Promise<T> { ...