Vue - Display components based on the parent data's state

I am currently working on a project where I need to dynamically render components based on the state of an array in the parent component (App.vue). As someone who is new to Vue and not very experienced with programming, I am uncertain if this is the most effective approach for my use case. Any advice on whether or not this is the right way to go about it would be greatly appreciated.

The project involves building a troubleshooter that consists of multiple questions. Each question is represented by a component with data structured similar to this:

data: function() {
    return {
        id: 2,
        question: "Has it worked before?",
        answer: undefined,
        requires: [
            {
                id: 1,
                answer: "Yes"
            }
        ]
    }
}

This specific question should only be displayed if the answer to question 1 was 'Yes'.

My main issue lies in figuring out how to conditionally render these components. Currently, my approach involves emitting an event from the child component when a question is answered, which then triggers an update in the parent component's array containing the status of all answered questions. Subsequently, each component needs to check this array to determine which questions have been answered and if they meet the required conditions to show the question.

So, my question is: How can I access and utilize the parent data to show/hide my components accordingly? And more importantly, is this method recommended or should I consider an alternative strategy?

Below is some sample code for reference:

App.vue

<template>
    <div id="app">
        <div class="c-troubleshooter">
            <one @changeAnswer="updateActiveQuestions"/>
            <two @changeAnswer="updateActiveQuestions"/>
        </div>
    </div>
</template>

<script>
import one from './components/one.vue'
import two from './components/two.vue'

export default {
    name: 'app',
    components: {
        one,
        two
    },
    data: function() {
        return {
            activeQuestions: []
        }
    },
    methods: {
        updateActiveQuestions(event) {
            let index = this.activeQuestions.findIndex( ({ id }) => id === event.id );
            if ( index === -1 ) {
                this.activeQuestions.push(event);
            } else {
                this.activeQuestions[index] = event;
            }
        }
    }
}
</script>

two.vue

<template>
    <div v-if="show">
        <h3>{{ question }}</h3>
        <div class="c-troubleshooter__section"> 
            <div class="c-troubleshooter__input">
                <input type="radio" id="question-2-a" name="question-2" value="yes" v-model="answer">
                <label for="question-2-a">Yes</label>
            </div>
            <div class="c-troubleshooter__input">
                <input type="radio" id="question-2-b" name="question-2" value="no" v-model="answer">
                <label for="question-2-b">No</label>
            </div>
        </div>
    </div>
</template>

<script>
export default {
    data: function() {
        return {
            id: 2,
            question: "Lorem Ipsum?",
            answer: undefined,
            requires: [
                {
                    id: 1,
                    answer: "Yes"
                }
            ]
        }
    },
    computed: {
        show: function() {
            // Check if requirements are met in the parent component and return true
            return true;
        }
    },
    watch: {
        answer: function() {
            this.$emit('changeAnswer', {
                id: this.id,
                question: this.question,
                answer: this.answer
            })
        }
    }
}
</script>

Answer №1

Conditionally Rendering Questions

In the realm of handling questions data, @Roy J proposes that it most likely belongs to the parent component. The parent controls all the data and determines which questions are to be displayed. Various strategies can be employed for this purpose:

  1. To display questions conditionally using v-if or v-show directly in the parent template:

Sometimes, the logic for displaying certain questions may not be entirely generic. It could rely on various factors such as user settings. In such cases, rendering the questions conditionally in the parent component would eliminate the need to access the entire questions data in each question component. An example code snippet is shown below:

<template>
    <div id="app">
        <div class="c-troubleshooter">
            <one @changeAnswer="updateActiveQuestions" v-if="displayQuestion(1)"/>
            <two @changeAnswer="updateActiveQuestions" v-if="displayQuestion(2)"/>
        </div>
    </div>
</template>

<script>
import one from './components/one.vue'
import two from './components/two.vue'

export default {
    name: 'app',
    components: {
        one,
        two
    },
    data: function() {
        return {
            activeQuestions: [],
        }
    },
    methods: {
        updateActiveQuestions(event) {
            let index = this.activeQuestions.findIndex( ({ id }) => id === event.id );
            if ( index === -1 ) {
                this.activeQuestions.push(event);
            } else {
                this.activeQuestions[index] = event;
            }
        },
        displayQuestion(index){
          // logic...
        }
    },
}
</script>
  1. Passing a reference to the previous question to every subsequent question:

If a question should only be visible when the preceding question has been answered or viewed, you can pass this information as a prop to each question component. This way, they know whether they should render themselves or not:

<template>
    <div id="app">
        <div class="c-troubleshooter">
            <one @changeAnswer="updateActiveQuestions"/>
            <two @changeAnswer="updateActiveQuestions" prev="activeQuestions[0]"/>
        </div>
    </div>
</template>

In the two.vue file:

props: ['prev'],
computed: {
    show: function() {
        return this.prev && this.prev.status === 'ANSWERED';
        // Additional logic specific to your requirements

    }
},
  1. Simply passing the complete set of data to the child components:

As demonstrated in your code snippets, passing the entire questions data as a prop to each individual question component allows you to utilize it within a computed property. While this approach may not be optimal, it gets the job done efficiently given that objects are passed by reference.

Utilizing a Generic Component:

The presence of separate components such as one.vue and two.vue for each question might seem cumbersome and impractical in the long run.

Creating modular components can prove challenging if the templates for each question vary significantly. Some questions may include images or custom elements while others do not.

If the templates share common HTML structures with defined headers or uniform layout elements like an 'ask' button, you should consider leveraging Vue slots to address this issue effectively.

Aside from template complexities, assuming that each question in your application can have multiple related sub-questions (e.g., two.vue containing question-2-a and

question-2-b>), a flexible and intricate data structure for managing questions data will be essential. Striving towards implementing a singular component like <code>question.vue
as opposed to multiple tailored question components will ultimately yield benefits.

Tips: Minimize Usage of Watchers

In the context of your two.vue template, relying on v-model to bind to the answer variable and subsequently employing a watcher to track changes can lead to convoluted code. Prefer utilizing @input or @change events directly on the

<input></wrapper"> element instead:</p>
<pre><code><input type="radio" id="question-2-a" name="question-2" value="yes" v-model="answer" @input="emitAnswer">

In lieu of the watcher, a method can handle emitting the event:

emitAnswer() {
this.$emit('changeAnswer', {
    id: this.id,
    question: this.question,
    answer: this.answer
})

Answer №2

Addressing a question of considerable scope, I aim to offer some practical advice.

Initially, data should be designated for managing internal state. Often, a component should utilize props for properties that might otherwise be considered part of its internal data. In this scenario, the questions should be coordinated by the parent component, which means the parent should hold the data. This approach facilitates the creation of a logical function to determine whether a question component should be displayed.

By having the parent manage the data, you can create a single question component that configures itself based on its props. Alternatively, you could have multiple types of question components (selecting the appropriate one using :is), but certainly, some of them can be reused by passing in their respective question/answer details.

To update answers, emit changes from the question components and allow the parent component to modify the value accordingly. I employ a settable computed property to enable the use of v-model within the component.

new Vue({
  el: '#app',
  data() {
    return {
      questions: [{
          id: 1,
          question: 'example question 1?',
          answer: null
        },
        {
          id: 2,
          question: 'example question 2?',
          answer: null,
          // This is connected because data is a function 
          show: () => {
            const q1 = this.questions.find((q) => q.id === 1);

            return Boolean(q1.answer);
          }
        },
        {
          id: 3,
          question: 'Still shown?',
          answer: null
        }
      ]
    };
  },
  components: {
    questionComponent: {
      template: '#question-template',
      props: ['props'],
      computed: {
        answerProxy: {
          get() {
            return this.answer;
          },
          set(newValue) {
            this.$emit('change', newValue);
          }
        }
      }
    }
  }
});
<script src="https://unpkg.com/vue@latest/dist/vue.js"></script>
<div id="app">
  <div class="c-troubleshooter">
    <question-component v-for="q in questions" v-if="!q.show || q.show()" :props="q" @change="(v) => q.answer = v" :key="q.id">
    </question-component>
  </div>
  <h2>Status</h2>
  <div v-for="q in questions" :key="q.id">
    {{q.question}} {{q.answer}}
  </div>
</div>

<template id="question-template">
  <div>
    {{props.question}}
    <div class="c-troubleshooter__input">
        <input type="radio" :id="`question-${props.id}-a`" :name="`question-${props.id}`" value="yes" v-model="answerProxy">
        <label :for="`question-${props.id}-a`">Yes</label>
    </div>
    <div class="c-troubleshooter__input">
        <input type="radio" :id="`question-${props.id}-b`" :name="`question-${props.id}`" value="no" v-model="answerProxy">
        <label :for="`question-${props.id}-b`">No</label>
    </div>
  </div>
</template>

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

What is the best way to implement a function on every item within a specific class?

I'm new to coding and I have a website that showcases job listings. I want to create a function that dynamically changes the logo of a company based on the first 50 characters of the company name mentioned. The current function only works for the firs ...

Automatic adjustment of line chart scales

Utilizing Google visualization charts in my Grails application has been a bit tricky. Specifically, when I add numerous rows of data to the line chart, it starts behaving strangely. Check out these screenshots https://i.sstatic.net/rWdwx.pnghttps://i.sstat ...

effectively showcasing information in a datatable by converting JSON data into objects

I am looking for a way to display balance information in datatables, but I have not been able to find a solution yet. <!DOCTYPE html> <html lang="en"> <head> <title>Comment</title> <meta charset="u ...

Troubleshooting: Issues with jQuery Validate plugin's rules method in Javascript

I'm encountering an issue with a simple javascript file that is supposed to run the rules method, but it's not working as expected. I have confirmed that my custom javascript file is being rendered correctly since input masking is functioning pro ...

Not all API results are being displayed by the Nextjs API function

I am facing an issue with rendering all the returns from the API in my Next.js application. The API, which is created in Strapi, is only displaying 1 out of the 3 possible returns. I am a beginner when it comes to using APIs and I suspect that the issue li ...

Unable to retrieve content using the query.ID in Next.js

I'm trying to figure out what is causing the issue in this code, but I can't seem to resolve it. My goal is to use the query.id inside getInitialProps to fetch some content. The fetching process works fine, but when an error occurs, I receive thi ...

Divide the number by the decimal point and then send it back as two distinct parts

Let's tackle a challenging task. I have a price list that looks like this: <span class="price">10.99€/span> <span class="price">1.99€/span> My goal is to transform it into the following format: <span class="price">10< ...

Save the JSON response from JavaScript to a different file extension in a visually appealing layout

I've created a script to generate both the CSR and private key. The response displayed in the <textarea> is well-formatted with newline characters (assuming familiarity with the correct CSR/Private key format). However, I'm encountering a ...

JavaScript | Calculating total and separate scores by moving one div onto another div

I have a fun project in progress involving HTML and Javascript. It's a virtual zoo where you can drag and drop different animals into their designated cages. As you move the animals, the total count of animals in the zoo updates automatically with the ...

Changing the Color of an Object3D Mesh in Three.js

Seeking advice on how to update the color of a Three.js Object3D. Initially created using MeshStandardMaterial, this object is later retrieved from the scene by its ID. Is it possible to change the color of the Mesh at this stage? If needing to replace th ...

Ensure that the date range picker consistently shows dates in a sequential order

Currently utilizing the vuetify date range picker component https://i.stack.imgur.com/s5s19.png At this moment, it is showcasing https://i.stack.imgur.com/GgTgP.png I am looking to enforce a specific display format, always showing the lesser date first ...

Is there a way for me to receive user inputs, perform mathematical operations on them, and then display the result of the calculation? Additionally, is there a way to ensure that the output value automatically updates

As a newcomer to HTML and JavaScript, I'm unsure how to approach this task. Here is what I have so far: <div class="inputs"> <label for="#arena2v2">2v2 Arena Rating:&#9;</label><input class="pvp" type="number" step="1" m ...

What is the syntax for replacing specific letters within a JavaScript string using JavaScript?

When I print data from an external JSON file, I am seeing strange characters like "Ã Å, Ã ¤, Ã ¶" instead of "Å, Ä, Ö". The file seems to have the wrong encoding, but unfortunately, I cannot change it since it is from an API. Is there a simple s ...

Converting counterup2 to pure vanilla JavaScript: step-by-step guide

Is there a way to convert the counterUp2 jQuery code to vanilla JavaScript? const counters = document.querySelectorAll('.counter'); function count(element) { let currentValue = 0; const targetValue = parseInt(element.innerText); let interv ...

Accessing class properties from JavaScript code

Can you retrieve the class values of a style element using Vue's script section? For example: .node-output02 { bottom: #{-2+$portSize/-2}px; left: #{$nodeWidth/3}px; } In the script, I'd like to achieve: const left = .node-output02. ...

Having trouble with the installation of Parcel bundler via npm

Issue encountered while trying to install Parcel bundler for my React project using npm package manager. The terminal displayed a warning/error message during the command npm i parcel-bundler: npm WARN deprecated [email protected]: core-js@<3 is ...

How to download an Excel file (xlsx) using AngularJS and WebApi

I am currently working on a project that requires me to download a report in xlsx format. Despite successfully generating the report file on the server and receiving it on the client side, I am facing an issue where the file is not opening and is resulting ...

Prevent span/button clicks by graying them out and disabling the ability to click using HTML and JQuery

I am facing a challenge with my two spans that reveal specific information when clicked. I want to make one span appear as a disabled button (greyed out) when the other span is clicked. I tried using $("#toggle-odd").attr("disabled", tr ...

The unit argument provided for Intl.NumberFormat() is not valid for electrical units such as volts and joules

After attempting to localize my web application, I have run into an issue with Intl.NumberFormat not working properly with electric units such as ampere, ohm, volt, and joule. The documentation provides examples and a list of available units. Despite fol ...

Formik's handleSubmit function appears to be malfunctioning

I have encountered a puzzling issue with Formik implementation in my two components. Despite implementing Formik in the same way for both components, I am facing a problem where `handleSubmit` works in one component but not in the other. You can check out ...