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

Running "vue ui" with Node.js v17.2.0 - A step-by-step guide

After updating to Node.js v17.2.0, I am facing issues with running "vue ui" in my project. The error message I receive indicates a problem with node modules: at Object.readdirSync (node:fs:1390:3) at exports.readdir (/usr/local/lib/node_modules/@vu ...

The object literal is restricted to defining existing properties, and 'id' is not a recognized property in the type 'FindOptionsWhere<Teacher>'

I encountered a persistent error within my teachers.service: Object literal may only specify known properties, and 'id' does not exist in type 'FindOptionsWhere | FindOptionsWhere[]'.ts(2353) FindOneOptions.d.ts(23, 5): The expected t ...

Learn how to effortlessly retrieve shared properties using Vue3 in Single File Components with the new `<script setup>` syntax

There are various ways to extract common props in Vue components, depending on whether you are using Vue 2 or Vue 3. However, when it comes to Single File Components (SFC), the approach differs. // common-props.js export default { commonProps: { enab ...

Is there a way to prevent Angular component lifecycle hooks like ngOnInit/ngOnDestroy from being triggered when nested within one another?

I am currently developing an application with a page structure that resembles the following: Displaying Objects retrieved from Firestore (similar to Instagram posts) Loading Comments (using the object's id to display comments sub-collection) Load ...

At what point does a prop become officially designated as having the same value as what was initially passed to the component

I am experiencing unreliable behavior in my component, where a prop is passed and I need to debug it: <my-component :number="someNumber" /> The number prop is defined in my-component through a standard declaration: prop: ["number" ...

Angular 2 and .NET Core 2.0 triggering an endless loop upon page refresh detection

My application, built with dotnet core 2.0 and angular 2, allows me to view member details. The process begins with a list page displaying all the members from a SQL Server database. Each member on the list has a link that leads to an individual details pa ...

The additional pieces of information transmitted in the state are not being accurately interpreted

I have constants set up that I want to store in the state: const day = "25/02/2020"; const timeStart = "08:00"; const timeEnd = "00:00"; In my Vuex file, I have the following setup: export default new Vuex.Store ({ s ...

Is there a way for me to showcase a particular PDF file from an S3 bucket using a custom URL that corresponds to the object's name

Currently, I have a collection of PDFs stored on S3 and am in the process of developing an app that requires me to display these PDFs based on their object names. For instance, there is a PDF named "photosynthesis 1.pdf" located in the biology/ folder, and ...

Testing the Angular router-outlet using Jasmine

When testing web-app navigation using Jasmine spec with RouterTestingModule, I am facing challenges with nested fixture.whenStable().then(() => {}). For instance: After clicking on multiple links where the router-outlet changes the displayed component ...

Leveraging RXJS for real-time response to keyboard and mouse click combinations in web

I am new to RXJS and looking for a way to drag an HtmlElement when the user presses the space key and then drags the element with the mouse. The dragging should be initiated by either pressing the SPACE key followed by a left click, or vice versa. The dra ...

Tips for ensuring a method is not invoked more than once with identical arguments

I'm grappling with a challenge in JavaScript (or typescript) - ensuring that developers cannot call a method multiple times with the same argument. For instance: const foo = (name: string) => {} foo("ABC") // ok foo ("123") ...

Arranging Angular Cards alphabetically by First Name and Last Name

I am working with a set of 6 cards that contain basic user information such as first name, last name, and email. On the Users Details Page, I need to implement a dropdown menu with two sorting options: one for sorting by first name and another for sorting ...

Preventing horizontal swiping while vertically scrolling on mobile devices

How can I prevent horizontal swipe functionality from interfering with vertical scrolling on a webpage? I have successfully blocked vertical scrolling but need help finding a solution for preventing horizontal swiping. Has anyone else encountered this issu ...

Guide on creating a single build in GitLab for a Vue application using multiple .env files

I currently have a .gitlab-ci.yml file set up to build my Vue application. The process involves building once and then deploying the dist folder to different environments: stages: - build - deploy_dev - deploy_stg - deploy_prd build: image: no ...

Guide on creating path aliases for Storybook webpack configuration

Currently, I am integrating Storybook with nextjs and webpack. Below is my configuration in .storybook/main.ts: import type { StorybookConfig } from '@storybook/nextjs'; const config: StorybookConfig = { ... framework: { name: '@sto ...

Exploring the methods for interpreting a JSON object within a Vue.js framework

Here is the json object I am working with: const faqs = [{'question1':'answer1'},{'question2':'answer2'}] In my Vue.js code, I am attempting to iterate through this data using a for loop within a div like so: <di ...

Typescript is throwing an error stating that utilizing 'undefined' as an index type is invalid

When working with TypeScript and React, I pass xs, sm, md, lg as props to the component. Each size corresponds to a specific pixel value (14px, 16px, 18px, 24px) that is then passed to an Icon component. The errors mentioned in point (1) occur at the line ...

Adding a component to a slot in Vue.js 3

My Goal I aim to pass the Component into the designated slot. The Inquiry How can I effectively pass the Component into the slot for proper rendering? It works well with strings or plain HTML inputs. Additional Query If direct passing is not feasible, ...

Issue with Typescript in react: JSX element lacks construct or call signatures

After upgrading TypeScript, I encountered the error mentioned above in one of my components. In that component's render method, I have the following code: render() { const Tag = props.link ? 'a' : 'div'; return ( < ...

Exploring Angular component testing through jasmine/karma and utilizing the spyOn method

I have been facing an issue while trying to test my component. Even though the component itself works perfectly, the test keeps generating error messages that I am unable to resolve. Here is the snippet of code that I am attempting to test: export cl ...