3
votes

As much as I'd like to share just share the relevant code in a simplified version, I can't. I have a slightly overdone todo app made in Vue. Here's the link to the repo: https://github.com/jaiko86/subtasks

The todo items are managed in the store:

export default {
  state: {
    workspaceIds: [], // IDs of workspaces
    tasksById: {}, // <--- this is the tasks I have
    detachedTask: null,
    focusedTaskId: null,
    currentWorkspaceId: null,
  },
  ...
}

In the App.vue file, I have the following computed property that the v-for will run through:

  computed: {
    taskIds() {
      const { currentWorkspaceId, tasksById } = this.$store.state;
      if (this.showOnlyLeafSubTasks) {
        return Object.values(tasksById)
          .filter(task => !task.subTaskIds.length)
          .map(task => task.id);
      } else if (currentWorkspaceId) {
        return tasksById[currentWorkspaceId].subTaskIds;
      }
    },
  },

Basically, the computed property will only return a list of tasks that are relevant to certain conditions, such as the workspace that I'm in. Each task has a unique ID, but for the sake of debugging, I've made it so that each task has the ID of the format task-#, and the task's input will have its task ID as a placeholder.

Here's the template in the App.vue:

<template>
  <div id="app">
    <!-- This is the top part -->
    <WorkspaceNav /> 

    <!-- this is the for-loop in question -->
    <TaskWrapper v-for="id in taskIds" :key="id" :id="id" :depth="0" />

    <!-- this is the filter that's on the right side -->
    <TaskFilters />
  </div>
</template>

The problem is that it won't render the items that the computed property returns.

I am failing miserably in trying to understand why there's a discrepancy between what's shown in the vue devtool and the console, and the view.

Here is the rendered view: The rendered view

Here is what's in the vue dev tool: Vue devtool showing state of App.vue

Here's what's printed in the console when I select the <App> component in the dev tool, thereby making it accessible via $vm0.taskIds:

["task-1", "task-2"]

Here's my custom function that prints the tasks hierarchically:

> printTree()
task-0
  task-1

  task-2

Here's the DOM for the relevant section of the code for the moment:


    <div id="app">
      <div class="workspace-nav">
        ...
      </div>
      <div data-v-76850a4a="" data-v-7ba5bd90="" class="leaf">
        <div data-v-76850a4a="" class="main-task depth-0">
          <!---->
          <div class="progress-indicator">
            <div class="checkmark gray-checkmark"></div>
          </div>
          <input placeholder="task-1" />
        </div>
        <div class="sub-tasks"></div>
      </div>
      <div class="filters">
        ...
      </div>
    </div>

So it's clear that no matter where I look, taskIds is returning a list of two items, but it's only rendering the first one.

1
computed property always should return some value, there u doing with conditional, in some case couldn't return nothingChristian Carrillo

1 Answers

2
votes

Here's one possible explanation...

At the point that the computed property returns its array the array only contains one item. You can confirm this in several ways. e.g. Try putting this in your template:

{{ taskIds }}

That array could subsequently change so that it contains two items but if the changes don't trigger the reactivity system them re-rendering will not occur.

Best guess is that tasksIds is returning tasksById[currentWorkspaceId].subTaskIds. That leaves two likely possibilities. Either subTasksIds isn't reactive, or it is being modified in a way that doesn't trigger the reactivity (e.g. direct modification by index).

Taking a look at your store code I don't see any obvious examples of the latter. However, this line from createTask does seem suspiciously like the former:

state.tasksById[task.id] = task;

This is adding task to an existing object under a (potentially) new key. This is one of the reactivity caveats, you can't add new properties to objects directly:

https://vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats

Instead you'd need to use Vue.set:

Vue.set(state.tasksById, task.id, task);

To better understand what's going on try adding the following logging to the end of createTask:

console.log(state.tasksById);

If you dig into the objects/arrays in the console you should see evidence of the Vue reactivity system. You may see references to Observer. Properties will have getters and setters and you need to click to see the property values. However, this will only happen if the object/array is reactive. Tasks that you add directly, without Vue.set, will just appear as normal objects/arrays in the console. It's worth taking some time to learn what the difference looks like as it makes debugging these kinds of problems much easier if you know what to look for.