'How to access a component's instance in an another instance of the same component?

I am trying to learn Vue 3 by creating a month planner app.

The entire has the following structure:

project structure

The calendar body component is as follows:

<template>
    <div class="grid-container">
        <div class="grid-item" v-for="(month, index) in months" :key="month">
            {{ month }} 
            <hr> 
            <TaskContainer  @toggle-side-panel='handleTogglePanel'
                            @close-side-panel='handleClosePanel'
                            @add-to-all-tasks='handleAddToAllTasks'
                            @del-from-all-tasks='handleDelFromAllTasks'
                            :allTasks=allTasks 
                            :activeMonth=(index+1) />
        </div>

        <div v-show="showPanel">
            <CalendarSidePanel :task='task'/>
        </div>
    </div>
</template>

<script>
    import TaskContainer from '../Task/TaskContainer.vue'
    import CalendarSidePanel from './CalendarSidePanel.vue'
    import { ref, reactive, toRefs } from '@vue/runtime-core'

    export default {
        name: 'CalendarBody',

        components: {
            TaskContainer,
            CalendarSidePanel
        },

        setup(props) {
            console.log('CalendarBody task ', props.task);
            const allTasks = ref([]);
            const showPanel = ref(false);
            const lastTask = ref(null);
            const task = ref(null);

            const state = reactive({
                months: ['January', 'February', 'March', 'April', 'May', 'June', 'July',
 'August', 'September', 'October', 'November', 'December']
            });

            function handleTogglePanel(tsk) {
                console.group('CalendarBody_handleTogglePanel');
                console.log('tsk ', tsk);

                if(tsk != null) {
                    task.value = tsk;
                    console.log('showPanel ', showPanel.value);
                    console.log('task ', task.value);
                    console.log('lastTask.value ', lastTask.value);

                    if(lastTask.value == null || lastTask.value != task.value.tName) {
                        console.log('lastTask is null or different ', lastTask);
                        showPanel.value = true;
                        lastTask.value = task.value.tName;
                        console.log('lastTask is assigned or changed', lastTask);
                    } else if(lastTask.value == task.value.tName) {
                        console.log('lastTask is the same ', lastTask);
                        showPanel.value = false;
                        lastTask.value = null;
                    }
                }
                console.groupEnd('CalendarBody_handleTogglePanel');
            }
            
            function handleAddToAllTasks(singleTask) {
                console.group('CalendarBody_handleAddToAllTasks');
                allTasks.value.push(singleTask);
                console.groupEnd('CalendarBody_handleAddToAllTasks');
            }

            function handleDelFromAllTasks(taskName) {
                console.group('CalendarBody_handleDelFromAllTasks');
                console.log('BEFORE: deleting from allTasks ', allTasks.value);
                allTasks.value = allTasks.value.filter(t => t.tName != taskName);
                console.log('AFTER: deleting from allTasks ', allTasks.value);                
                console.groupEnd('CalendarBody_handleDelFromAllTasks');
            }

            function handleClosePanel(taskName) {
                console.group('CalendarBody_handleClosePanel');
                if(showPanel.value && taskName == lastTask.value) {
                    showPanel.value = false;
                    lastTask.value = null;
                }
                console.groupEnd('CalendarBody_handleClosePanel');
            }
        
            return {
                ...toRefs(state),
                allTasks,
                task,
                showPanel,
                handleClosePanel,
                handleTogglePanel,
                handleAddToAllTasks,
                handleDelFromAllTasks
            }
        }
    }
</script>

<style scoped>
    .grid-container {
        display: grid;
        grid-template-columns: auto auto auto auto auto auto auto auto auto auto auto auto auto;
        background-color: #2196F3;
        padding: 5px;
        box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.5);
    }

    .grid-item {
        background-color: #2196F3;
        margin: 2px;
        font-size: 30px;
        text-align: center;
        border: 0.5px solid rgba(0, 0, 0, 0.8);
    }
</style>

Task container:

<template>
    <div>
        <TaskHeader @add-tasks="handleAddTasks" />
        <div>
            <TaskBody   :tasks=tasks
                        @add-dragged-task='handleAddDropTask'
                        @toggle-side-panel='handleTogglePanel'
                        @delete-task='handleDeleteTask' />
        </div>
    </div>
</template>

<script>
    import TaskHeader from './TaskHeader.vue';
    import TaskBody from './TaskBody.vue';
    import { reactive, ref } from '@vue/reactivity';
    
    export default {
        name: 'TaskContainer',

        components: {
            TaskHeader,
            TaskBody
        },

        setup(props, context) {
            const monthTasks = reactive({});
            const tasks = ref([]);

            function handleRemoveDropTask(droppedTaskName) {
                console.group('TaskBody_handleRemoveDropTask');
                console.log('droppedTaskName ', droppedTaskName);
                const dropTask = props.allTasks.find(t => t.tName == droppedTaskName);
                console.log('dropTask ', dropTask);

                monthTasks[dropTask.tStartMonth] = monthTasks[dropTask.tStartMonth]
                                                                      .filter(t => t.tName != droppedTaskName);
                console.log('monthTasks ', monthTasks);

                console.log('BEFORE: tasks ', tasks.value, 'lenght ', tasks.value.length);
                tasks.value = tasks.value.filter(t => t.tName != droppedTaskName);
                console.log('BEFORE: tasks ', tasks.value, 'lenght ', tasks.value.length);

                console.group('TaskBody_handleRemoveDropTask');
            }

            function handleAddTasks(singleTask) {
                console.group('TaskContainer_handleAddTasks');
                tasks.value.push(singleTask);
                monthTasks[props.activeMonth] = tasks.value;
                console.log('adding to month\'s tasks ', monthTasks);
                context.emit('add-to-all-tasks', singleTask);
                console.groupEnd('TaskContainer_handleAddTasks');
            }
            
            function handleDeleteTask(taskName) {
                console.group('TaskContainer_handleDeleteTask');
                console.log('taskName ', taskName);
                console.log('monthTasks ', monthTasks[props.activeMonth].filter(t => t.tName != taskName));
                monthTasks[props.activeMonth] = monthTasks[props.activeMonth].filter(t => t.tName != taskName);
                console.log('monthTasks ', monthTasks);
                console.log('BEFORE: tasks ', tasks.value, 'lenght ', tasks.value.length);
                tasks.value = tasks.value.filter(t => t.tName != taskName);
                console.log('BEFORE: tasks ', tasks.value, 'lenght ', tasks.value.length);
                context.emit('close-side-panel', taskName);
                context.emit('del-from-all-tasks', taskName);
                console.groupEnd('TaskContainer_handleDeleteTask');
            }

            function handleAddDropTask(dropTaskName) {
                console.group('TaskContainer_handleAddDropTask');
                const dropTask = props.allTasks.find(t => t.tName == dropTaskName);

                dropTask.tStartMonth = props.activeMonth;
                console.log('dropTask ', dropTask);
                console.log('props.allTasks ', props.allTasks);

                console.log('monthTasks ', monthTasks);
                console.log('monthTasks ', monthTasks[props.activeMonth]);
                if(monthTasks[props.activeMonth] == undefined) {
                    tasks.value = []
                    monthTasks[props.activeMonth] = tasks.value;
                }
                console.log('monthTasks ', monthTasks[props.activeMonth].filter(t => t.tName != dropTaskName));
                monthTasks[props.activeMonth] = monthTasks[props.activeMonth].filter(t => t.Name != dropTaskName);
                monthTasks[props.activeMonth].push(dropTask);
                console.log('monthTasks ', monthTasks);

                console.log('BEFORE: tasks ', tasks, 'length ', tasks.value.length);
                tasks.value = tasks.value.filter(t => t.Name != dropTaskName);
                tasks.value.push(dropTask);
                console.log('AFTER: tasks ', tasks, 'length ', tasks.value.length);
                console.groupEnd('TaskContainer_handleAddDropTask');
            }

            function handleTogglePanel(tsk) {
                console.group('TaskContainer_handleTogglePanel');
                context.emit('toggle-side-panel', tsk);
                console.groupEnd('TaskContainer_handleTogglePanel');
            }

            return {
                tasks,
                handleAddTasks,
                handleTogglePanel,
                handleDeleteTask,
                handleAddDropTask,
                handleRemoveDropTask
            }
        },

        props: ['allTasks', 'activeMonth'],
    }
</script>

<style scoped>

</style>

One of the features is the ability to drag a task from one month to another month. As can be seen from the picture I am able to drag and drop from Jan month to Feb month, there by creating the task in the Feb month, but in order to remove the task from the Jan month, I need access to the Jan month's TaskContainer's reactive tasks or monthTasks variable in Feb month as each TaskContainer is an instance created inside the v-for of the CalendarBody's template.

output

My questions are:

  1. How can I access Jan months instance (specifically tasks or monthTasks variables) inside Feb month, so that I can remove the task 1 from the variables, so that the task doesn't remain in Jan.
  2. Is this the correct way to create TaskContainer component? That is, declaring TaskContainer inside the v-for thereby creating a instance for each month or should I have hard coded TaskContainer 12 times?
  3. I read that a prop passed down from the parent should not be modified in child but rather only in the parent. I am finding this bit hard to implement as it is much easier to simply change a value where I want instead of emitting event to the parent for every small change. As mine is bit nested I have to emit from child to grandparent or even great grandparent. Any suggestions on how to emit directly to the component I need, something similar to provide/inject for props but for emitting?

PS:

  1. I am using just the base VUE without any other framework/library.
  2. Please ignore the CSS also as it is only a makeshift.


Solution 1:[1]

  1. You can use vuex (vue store) for your app. So you can track all the information app-wide. Plan B should be use your parent component.

    this.$parent

will give you parent component but be aware if you wrap this child component you should call 1 more $parent.

  1. V-for should be correct way to follow.
  2. You can't modify props in the child Vue will throw an error. As I said you can create vuex store or you can cal as many as you like $parent and handle all items in the parent

Solution 2:[2]

This is a perfect example where using a store makes your life easier. Official Vue recommendation is https://pinia.vuejs.org/

To answer your questions specifically:

  1. You should keep all this state in a store and then you can easily access it from any component without worrying how to access some component specifically

  2. It is completely OK to use v-for to create multiple instances of a component.

  3. When using a store you will have avoided issues with passing props through multiple layers of components aka "prop drilling". Modifying data in a Pinia store is as simple as modifying it directly or by using $patch https://pinia.vuejs.org/core-concepts/state.html#mutating-the-state

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 gguney
Solution 2 quadmachine