Home > Blockchain >  Accessing ref values from composable (part 2)
Accessing ref values from composable (part 2)

Time:12-07

This is a follow-on question from my previous question here: Accessing ref values from composable

Here is my App code:

<template>
  {{ reversed_names }}
</template>

<script>
import { ref, watchEffect } from "@vue/runtime-core";
import getData from "./composables/getData";
import reverseNames from "./composables/reverseNames";

export default {
  name: "App",
  setup() {
    var filenames = ["test1.json", "test2.json"];
    const { names_data, error, load_data } = getData(filenames);
    load_data();

    const reversed_names = ref({});

    watchEffect(() => {
      const reversed_names = reverseNames(names_data.value);
      console.log("revnames inside watchEffect", reversed_names.value);
    });
    console.log("revnames outside watchEffect", reversed_names);

    return { names_data, reversed_names };
  },
};
</script>

and here is the reverseNames function:

import { ref } from "@vue/runtime-core";

const reverseNames = (names_data) => {
  const reverse_names = ref({})
  var filenames = Object.keys(names_data)
  for (let f in filenames) {
    var filename = filenames[f]
    reverse_names.value[filename] = {}
    var members = names_data[filename]["members"]
    for (let n in members) {
      let name = members[n];
      var reverseName = name.split("").reverse().join("");
      reverse_names.value[filename][name] = reverseName
    }
  }
  return reverse_names
};

export default reverseNames

The watchEffect is there so I can use names_data once the data is loaded from a file (see linked question above). The code works fine up to the call to reverseNames. names_data.value contains the following:

{ "test1.json": 
    { "members": 
        { "0": "Alice", 
          "1": "Bob", 
          "2": "Charlie" 
        } 
    }, 
  "test2.json": 
    { "members": 
        { "0": "David", 
          "1": "Elizabeth", 
          "2": "Fred" 
        } 
    } 
}

Here's the confusing part:

Inside the watchEffect function, the console shows the correct return dictionary from the function. However, outside the watchEffect function, the console just shows the empty ref({}) defined before the watchEffect() call.

It is the empty dictionary that is returned by setup() and displayed in the template.

How can I get the setup() to return the reversed_names from inside the watchEffect?

CodePudding user response:

const reversed_names outside and inside watchEffect function scope are different, unrelated variables. reversed_names inside function scope shadows another one from parent scope.

A ref is a pattern that makes use of that in JavaScript objects are passed by reference, not by value. This means that it's a ref and not a value that needs to be passed in every place that needs to preserve reactivity and be aware of that it's used in the context of Vue application. A variable that holds a ref should be never reassigned, instead value property needs to be reassigned.

reverseNames is not general purpose helper function but is specific to Vue, so can accept a ref. reverse_names doesn't serve a good purpose because it's not passed anywhere and therefore doesn't benefit from using ref pattern. It could be:

const reverseNames = (namesDataRef) => {
  const reverse_names = {} // not a ref
  var filenames = Object.keys(namesDataRef.value)
    reverse_names[filename] = {}
      ...
      reverse_names[filename][name] = reverseName
    }
  }

  namesDataRef.value = reverse_names;
  // doesn't need to return anything because it uses refs
}

and

const reversed_names = ref({});

watchEffect(() => {
  reverseNames(reversed_names);
  console.log("revnames inside watchEffect", reversed_names.value);
});

This is an antipattern. This would make sense if reverseNames were using asynchronous side effects or anything that could trigger changes that cannot be tracked during reverseNames call, similarly to another question. But it's synchronous and doesn't affect anything but a single ref, so there are no reasons for it to use reactivity. Instead, reactivity could be lifted to a caller:

const reverseNames = (namesData) => {
  const reverse_names = {} // not a ref
  ...
  return reverse_names
}

and

const reversed_names = ref({});

watchEffect(() => {
  reversed_names.value = reverseNames(names_data.value);
  console.log("revnames inside watchEffect", reversed_names.value);
});

Without a side effect (console.log), there would be no need for watchEffect, it could be a computed ref:

const reversed_names = computed(() => reverseNames(names_data.value));
  • Related