Is it possible to transfer the reactivity of a Vue ref to another ref while reassigning it?

Below is a simplified version of my Vue component:

<template>
  <div @click="loadEvents">{{ loading }}</div>
</template>

<script setup>
import { ref } from 'vue'
let loading = ref(false)

loadEvents()
function loadEvents() {
  const res = backendApi.getEvents(selectedDate.value)
  loading = ref(res.loading)
}
</script>

The function "backendApi.getEvents" is structured like this:

getEvents() {
  const loading = ref(true)
  axios.get(...).then(r => loading.value = false)
  
  return { loading }
}

Upon the initial page load, the value of loading displays correctly as "true" and then changes to "false" after the request completion. However, when the "loadEvents" function is triggered again by clicking on the div, the "loading" value remains "false" and does not update in the DOM. What am I doing incorrectly and how can I resolve this issue? I attempted

loading.value = res.loading.value
, but it only changes "loading" to "true" without transitioning back to "false" once the request finishes.

Answer №1

Here is a brief summary:

You cannot reassign a reactive object because it will break the reactivity and cause the old references to lose their connection to the object's value. Therefore, it is recommended to declare all reactive variables using const.

It appears that the logic you have provided is overly complex. In my understanding, the loading variable should be set to true when axios is in the process of loading data. Make sure that your imports are correct:

<template>
  <div @click="loadEvents">{{ loading }}</div>
</template>

<script setup>

const loading = ref(false);

loadEvents()

async function loadEvents() {
  loading.value = true;
  const results = await backendApi.getEvents(selectedDate.value);
  
  // perform actions with results here
  
  loading.value = false;
}

function getEvents() {
  return axios.get(...);
}

</script>

However, there is room for improvement by refactoring the code and reusing the loading variable within the backend api:

// backend-api.js file:

export const loading = ref(false);

export const backendApi = {
  getEvents(){
    return this.callApi(...);
  },
  async callApi(...args){
    loading.value = true;
    const result = await axios.get(...args);
    loading.value = false;
    return result;
  }
};

// component file
<template>
  <div @click="loadEvents">{{ loading }}</div>
</template>

<script setup>

import {loading, backendApi} from './backend-api.js';

loadEvents()

async function loadEvents() {
  const results = await backendApi.getEvents(selectedDate.value);
  
  // perform actions with results here
  
}

</script>

UPDATE
After some discussion with the original author, a new solution has emerged.
It was highlighted that each backend call requires its own loading reference. Moving the loading variable into the backend api might not be ideal as it can introduce dependency coupling. One recommended approach is to use setters with promise-based actions. For instance, creating a utility function like promiseRef() can help manage loading states independently. Here is how the modified setup looks:

<script setup>
    
  import { backendApi } from './backend-api.js';
  import { promiseRef } from './utils.js'
  
  const loading = promiseRef();

  async function loadEvents(){
     const result = await (loading.promise = backendApi.getEvents());
     
    // utilize the fetched results
     
  }

</script>

<template>
  <button @click="loadEvents">{{ loading }}</button>
</template>

backend-api.js:

// This is a simulated API call
export const backendApi = {
  getEvents(){
    return new Promise(resolve => setTimeout(resolve, 2000));
  }  
}

utils.js:

import { ref } from 'vue';
export const promiseRef = value => {
  
  const loading = ref(value || false);
  let promise;
  
  Object.defineProperty(loading, 'promise', {
    get(){
      return promise;
    },
    set(val){
      loading.value = true;
      (promise = val).then(() => loading.value = false);
    }
  });
  
  return loading;
  
};

View a working example on Vue3 gist!

Answer №2

Looking for a solution? Check out Composables. There is a helpful example in the documentation that demonstrates how to use fetch.

To create your own composable, follow the example and maintain reactivity by utilizing reactive(useComposable) and computed:

import { computed, reactive } from 'vue';
import { useLoadEvents } from './loadEvents.js';

const events = reactive(useLoadEvents());
const loading = computed(() => events.loading);

You can also skip using computed and directly reference events.loading in the 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

Having trouble configuring individual route files in Express JS

I'm having trouble separating my login route from the default app.js and route/index.js files. When I try to access localhost:3000/login, I keep getting a 404 Not Found error. I've searched on StackOverflow for similar questions and tried follow ...

Issues with Disabling the Browser's Back Button with jquery

I have been attempting to prevent the back button from functioning in a specific Browser, Microsoft Bing. Interestingly, my code works only if I click somewhere in the window first. If I don't click anywhere and try to go back directly, it still allow ...

The event handler attached to the 'click' event will only be triggered upon the second click

After implementing the script, I noticed a strange behavior. When I click it on load, everything works perfectly. However, when I click it via a dynamically created element, the HTML seems to shift or stretch on the first click before returning to normal. ...

Tips for simulating a service in Angular unit tests?

My current service subscription is making a promise: getTaskData = async() { line 1 let response = this.getTaskSV.getTaskData().toPromise(); line 2 this.loading = false; } I attempted the following approach: it('should load getTaskData', ...

How to retrieve the button value in HTML

One of the HTML components I am working with is a button that looks like this: <button>Add to cart</button> My goal is to retrieve the text within the button, which in this case is "Add to cart." To achieve this, I need to extract this value ...

Is it possible to implement pagination for loading JSON data in chunks in jsGrid?

Currently, I am utilizing jsgrid and facing an issue with loading a JSON file containing 5000 registries into a grid page by page. My goal is to display only 50 registries per page without loading all 5000 at once. Even though I have implemented paging in ...

How can I disable auto-fill for password input fields? Setting autocomplete="off" doesn't seem to be working

Hey there, I'm having some trouble with the autocomplete feature in Google Chrome. Can anyone suggest an alternative way to disable autocomplete for password fields? By the way, my project is using Vue.js. ...

The standard TextField functionality was disrupted by the update to MUI v5

After typing a comment in the TextField and trying to click Done, nothing happens because the TextField still has focus. The first click removes the focus, while a second click is needed to complete the action. <TextField id={'generalCom ...

Position the button at the bottom of the page with MUI v5 in a React application

How can I ensure the button is always positioned at the center bottom of the page, regardless of the content height? This is a snippet from my code: const useStyles = makeStyles({ button: { bottom: 0, right: 0, position: "absolute" ...

``JsViews and AngularJS: A Comparison"

I'm exploring the possibility of creating a single page application and came across jsViews/jsRender which seems very promising as it approaches Beta. As someone new to SPA development, I'm interested in understanding how jsViews stacks up agains ...

Is it advisable to optimize your SEO by placing the JavaScript code at the bottom of your webpage?

Is this just an urban legend or is it actually true? I've heard that when web crawlers analyze a webpage, they have a time limit to capture all available code (like html) before moving on to the next page. If the JavaScript code is in the head sectio ...

Any suggestions on how to display the data names field in the vue-multiselect plugin on the edit page?

Currently, I am working on the edit page for employees on my vue laravel SPA. The create employees page is already set up and I am utilizing the vue-multiselect plugin () to display data. At present, I have managed to show the employee ID's from an ar ...

There was a type error that occurred because the property 'addEventListener' could not be read from an undefined value

Encountering an issue with the JavaScript libraries three.js and OrbitControls.js despite following a tutorial: However, an error is being displayed in the console: Uncaught TypeError: Cannot read property 'addEventListener' of undefined at ne ...

I'm interested in exploring whether p5.js allows for the creation of a class that can draw sub-classes within itself. One idea I have in mind is to create a 4x4 grid composed of individual

My goal is to create a game similar to Tetris, where the pieces are composed of smaller blocks that share attributes. My current progress includes: export class SquareTetromino { [x: string]: any; constructor(x, y, w, h) { ... } ...

Tips for arranging images in a horizontal layout using FlatList in React Native

Is there a way to display feed images horizontally instead of vertically in FlatList? I've tried wrapping the images in a view with flex-direction set to row, as well as adding horizontal={true} to the FlatList, but nothing seems to work. Any suggesti ...

Looking to dynamically generate HTML tags using jQuery and JSON?

Looking for help with inserting HTML code into a div using jQuery. <div id="addme"></div> Here is some HTML with PHP: <div class="col-md-4 product secondproduct"> <div class="images1"> <a href="<?php echo base_u ...

The Ajax script is malfunctioning

Currently, I have a program that requires the user to input a city and country. The program then checks the database to see if the city already exists - displaying a warning message using ajax if it does, or adding the city to the database if it doesn&apos ...

develop custom button in vue-good-table-next

I am attempting to set up a datatable using vue-good-table-next and Vue 3 in Laravel 9. I have successfully populated all the data using Axios. However, I am facing an issue where I need to implement custom buttons for editing and removing entries. Despi ...

Notification fails to display following POST request

Whenever the select menu displays "Please select your country" and I click the checkout button, an alert pops up. But if I select Canada, then go back to selecting "Please select your country" and press checkout, no alert appears. I need the alert to alway ...

Error: The Vue bind object property cannot be read because it is undefined in the class binding

I'm attempting to associate a class with an object property, but I encounter an issue when trying to trigger the @click event on the list object - the console states that it cannot read the isSelected property of the object. My goal is to activate a c ...