A guide to accessing parent attributes in Vue3 using typescript

Within my child component, I am facing an issue where I need to access the parent object but the commented lines are not functioning as expected.

The structure of AccordionState is defined below:

export type AccordionKeys = | "open" | "disabled";

export type AccordionState = {
    [key: string]: {[key in AccordionKeys]: boolean; };
}

Here is the code snippet from the child component:

<script lang="ts">
    import { AccordionState } from "@/types/global";
    import { defineComponent, useAttrs } from "vue";

    export default defineComponent({
      name: "AccordionPane",
      props: {
        panelName: {
          type: String,
          required: true,
        },
      },
      computed: {
        open(): boolean {
          // const attrs = useAttrs();
          // const state = attrs.state as AccordionState;
          // return state[this.panelName].open && !state[this.panelName].disabled;
          return true;
        },
    disabled(): boolean {
      // const attrs = useAttrs();
      // const state = attrs.state as AccordionState;
      // return state[this.panelName].disabled;

      return false;
    },

In the parent component. The implementation of computed property getProps verifies that prop.state is being defined correctly, However, the commented section in AccordionPane results in a null value for variable "state". Due to limited TypeScript examples online and unstructured JavaScript examples, I'm finding it challenging to adapt them to TypeScript.

  <template>
    <div class="accordionControl">
      <slot ref="content"></slot>
    </div>
    <textarea class="debug" v-model="getProps"></textarea>
  </template>

  <script lang="ts">

  import { type AccordionState } from '@/types/global';
  import { defineComponent, type PropType } from 'vue';

  export default defineComponent({
    name: "AccordionControl",
    props: {
      state: {
        type: Object as PropType<AccordionState>,
        required: true,
      },
      isVert: {
        type: Boolean,
        default: false,
      },
      allOpen: {
        type: Boolean,
        default: false,
      }
    },
    computed: {
      getProps(): string {
        return JSON.stringify(this.$props, null, '  ');
      }
    }
  });
  </script>

Check out the working example provided in this JavaScript link:

Visit here!

Answer №1

I opted for a service in the end.

  import type { 
    AccordionChildMap,
    AccordionStates,
    AccordionData,
    AccordionProps,
    AccordionKeys,
    AccordionAction,
    AccordionPanelState,
  } from '@/types/global';

  class AccordionServiceInstance {
    #childMap: AccordionChildMap;
    #datas: AccordionStates;
    #props: { [key: string] : AccordionProps };
    #actionHandlers: { [key: string] : AccordionAction }
    #nameIndex: number;
    
    constructor() {
      this.#childMap = {};
      this.#datas = {};
      this.#props = {};
      this.#nameIndex = 0;
      this.#actionHandlers = {};
    }

    registerParent(data: AccordionData, isVert: boolean, allOpen: boolean) : string {
      console.log("ParentHeight: " + data.height);
      const parentName = "accordion" + this.#nameIndex++;
      this.#props[parentName] = {
        isVert: isVert,
        allOpen: allOpen,
      } 
      this.#datas[parentName] = data;
      return parentName;
    }

    registerChild(childName: string, onChange: AccordionAction, headerHeight: number) : string {
      
      let foundParent = "";
      console.log("set headerHeight: " + headerHeight);
      
      const keys = Object.keys(this.#datas)
      for (let i=0; i < keys.length && foundParent == ""; i++) {
        const key = keys[i];
        const parentKeys = Object.keys(this.#datas[key].state);
        if (parentKeys.indexOf(childName) > -1) {
          foundParent = key;
          this.#childMap[childName] = key;
          this.#actionHandlers[childName] = onChange;
          this.#datas[foundParent].headerHeight = headerHeight;
        } else {
          console.log("AccordionService could not find parent for: " + childName);
        }
      }
      return foundParent;
    }

    getPanelHeight(childName: string) : string {
      const stateName = this.#childMap[childName];
      const data = this.#datas[stateName]
      const state = data.state;
      const props = this.#props[stateName];
      const childState = state[childName];
      const childCount = Object.keys(state).length;
      return (childState.disabled || 
        !childState.open) ? "0" :
          props.allOpen ? "auto" : (data.height - childCount * (data.headerHeight + 8) + "px");
    }

    getChildState(childName: string) : AccordionPanelState {
      const stateName = this.#childMap[childName];
      return this.#datas[stateName].state[childName];
    }

    getChildProps(childName: string) : AccordionProps {
      const stateName = this.#childMap[childName];
      return this.#props[stateName];
    }

    getData(parentName: string) : AccordionData {
      return this.#datas[parentName];
    }

    setHeight(parentName: string, height: number) : void {
      console.log("set height: " + height);
      this.#datas[parentName].height = height;
      const state = this.#datas[parentName].state;

      Object.keys(state).forEach( (x) => {
        console.log(x + "| open:" + state[x].open + "| disabled:" + state[x].disabled)
        this.#actionHandlers[x](state[x].open, state[x].disabled);
      });
    }

    changeChildState(childName : string, property: AccordionKeys, value: boolean) : void {
      const stateName = this.#childMap[childName];
      const props = this.#props[stateName];
      const state = this.#datas[stateName].state;
      if (!props.allOpen && property == "open" && value) {
        Object.keys(state).forEach( (x) => {
          if (x == childName) {
            state[x].open = true;
          } else {
            state[x].open = false;
          }
        });
      } else {
        state[childName].open = value;
      }
      Object.keys(state).forEach( (x) => {
        console.log(x + "| open:" + state[x].open + "| disabled:" + state[x].disabled)
        this.#actionHandlers[x](state[x].open, state[x].disabled);
      });
    }
  }

  export const AccordionService = new AccordionServiceInstance();

Utilizing the control:

 <AccordionControl :state="{
    colorWheel: { open: true, disabled: false },
    colorMixer: { open: false, disabled: false },
    colorSelector: { open: false, disabled: true },
  }"
 >
   <AccordionPane panelName="colorWheel">
      <template v-slot:header>
        <h2>Create Color Scheme</h2>
      </template>
      <template v-slot:content>
        Stuff here
      </template>
    </AccordionPane>
    <AccordionPane panelName="colorMixer">
      <template v-slot:header>
        <h2>Color Mixer</h2>
      </template>
      <template v-slot:content>
        <h1>Stuff here</h1>
      </template>
    </AccordionPane>
    <AccordionPane panelName="colorSelector">
      <template v-slot:header>
        <h2>Assign / Select colors</h2>
      </template>
      <template v-slot:content>
        <h1>Stuff here</h1>
      </template>
    </AccordionPane>
  </AccordionControl>

There is more to be elaborated on in the answer, like the stylesheets and the controls, so I hope you can see this:

edit: new public link provided, you should have access to it.

https://codesandbox.io/p/github/geewhizbang/iconBuilder/main?layout=%257B%2522sidebarPanel%2522%253A%2522EXPLORER%2522%252C%2522rootPanelGroup%2522%253A%257B%2522direction%253A%2522horizontal%2522%252C%2522type:%2522PANEL_GROUP%2522%252C%2522id:%2522ROOT_LAYOUT%2522%252C%2522panels%2522:%255B%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522JPSCdi..."

I trust this isn't a workaround.

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

Utilizing Sharepoint Online SPFX Web parts with React: A guide to selecting scripts dynamically based on environment requirements

Would it be possible for me to dynamically choose which script to utilize in my web component? This is how I currently have my imports set up: import * as jQuery from 'jquery'; import 'jqueryui'; Here's what I am aiming to achie ...

Error encountered while upgrading to Angular 5: splitHash issue

Currently in the process of transitioning from Angular 4.x to 5.x, I have encountered the following error: main.81bcdf404dc22078865d.bundle.js:1 Uncaught TypeError: i.splitHash is not a function at Object.t.parseUrl (main.81bcdf404dc22078865d.bundle.js:1) ...

When I delete the initial element from the array, the thumbnail image disappears

Using react-dropzone, I am attempting to implement image drag and drop functionality. The dropped image is stored in the React state within a files array. However, a problem arises when removing an image from the array causing the thumbnails of the remain ...

Differences between useFormState and useForm in Next.js version 14

Currently, I am intrigued by the comparison between using the react hook useForm and the react-dom useFormState. The Nextjs documentation suggests utilizing useFormState, but in practice, many developers opt for the react hook useForm. I am grappling with ...

Tips for aligning actions to the right of a Vue3 Quasar horizontal card

Looking for help on getting the actions aligned to the far right side while keeping a small title using Quasar classes or Flexbox. When I try replacing the text on the right, it shifts the actions over which is not what I want. Codepen: https://codepen.io ...

The Angular performance may be impacted by the constant recalculation of ngStyle when clicking on various input fields

I am facing a frustrating performance issue. Within my component, I have implemented ngStyle and I would rather not rewrite it. However, every time I interact with random input fields on the same page (even from another component), the ngStyle recalculate ...

Creating an enum in TypeScript can be accomplished by using the enum

What transformations do enums undergo during runtime in the TypeScript environment? Fruit.ts enum Fruit {APPLE, ORANGE}; main.ts let basket = [Fruit.APPLE, Fruit.ORANGE]; console.log(basket); The resulting main.js file remains identical to the .ts ver ...

I am experiencing issues with my HTML select list not functioning properly when utilizing a POST service

When using Angularjs to automatically populate a list with *ngFor and then implementing a POST service, the list stops functioning properly and only displays the default option. <select id="descripSel" (change)="selectDescrip()" > <option >S ...

Troubleshooting a useContext error in Next.js with TypeScript

I've been working on an app using next.js for the frontend, and I encountered an issue while trying to stringify an object. Here's a snippet of the error message: Argument of type '{ auth: dataObject; }' is not assignable to parameter o ...

Error in Angular6: Why can't handleError read injected services?

It appears that I am facing an issue where I cannot access a service injected inside the handleError function. constructor(private http: HttpClient, public _translate: TranslateService) { } login(user: User): Observable<User> { ...

Is there a way to use dot notation in TypeScript for a string data type?

I'm currently in the process of developing a function API with a specific format: createRoute('customers.view', { customerId: 1 }); // returns `/customers/1` However, I am facing challenges when it comes to typing the first argument. This ...

What's the best way for me to figure out whether type T is implementing an interface?

Is it possible to set a default value for the identifier property in TypeScript based on whether the type extends from Entity or not? Here's an example of what I'm trying to achieve: export interface Entity { id: number; // ... } @Compon ...

Vue is functioning properly, however warnings continue to appear in the code

A warning is appearing stating "Avoid mutating a prop directly," and I am aware that this can be resolved by using data properties or computed methods as mentioned in the Vue Official Documentation. However, I'm unsure how to implement either of these ...

The 'Event' type is lacking the specified properties when compared to the 'CdkDragDrop<string[], string[]>' type

After exploring the reorderable columns demo on stackblitz, I came across this interesting code snippet. Specifically, here is the HTML snippet: <table mat-table [dataSource]="dataSource" cdkDropList cdkDropListOrientati ...

What is the proper way to invoke render functions using Vue 3 composition API?

During my time with Vue 2, I would typically call render() in this manner: export default { mounted(){ ... }, render(){ ... }, methods(){ ... } } Now that I'm exploring Vue 3 and the composition API, I ...

What is the process for importing types from the `material-ui` library?

I am currently developing a react application using material-ui with typescript. I'm on the lookout for all the type definitions for the material component. Despite attempting to install @types/material-ui, I haven't had much success. Take a look ...

When working with an array of objects in Vue.js, what is the optimal approach: replacing the entire array or modifying individual values?

I am currently utilizing Vue and Vuex to dynamically generate components from an array retrieved from SQLite using the library better-sqlite3. let table=[ { id:1, column_1:'data', column_2:'data', column_3:{'1&apo ...

The specified "ID" type variable "$userId" is being utilized in a positional context that is anticipating a "non-null ID" type

When attempting to execute a GraphQL request using the npm package graphql-request, I am exploring the use of template literals. async getCandidate(userId: number) { const query = gql` query($userId: ID){ candidate( ...

Organize data in a Vue.js table

Currently facing an issue with sorting my table in vue.js. Looking to organize the table so that campaigns with the highest spend are displayed at the top in descending order. Here is the code I'm working with: <template> <div class=" ...

What is causing my React-Testing Library queries to not work at all?

In my current project, I am using Jest along with Testing-Library to create UI unit tests. One issue that I encountered was that the components were not rendering on the DOM. After some investigation, I found that the main culprit was a component called & ...