Andy Jarrett // Code. Develop. Create.

Why Updating Alpine.js State with Direct Reassignment Fails (and How to Fix It)

Green mountain under blue sky
Photo by Trevor Wilson on Unsplash

Why Updating Alpine.js State with Direct Reassignment Fails (and How to Fix It)

Reactivity is amazing, but when it fails its frustrating. And with that a stupid issue hit me recently because of direct reassignment of a variable that I did. In my defence I was reassigning the property this.formFields after fetching JSON. So, this blog post is here to remind me to check the obvious first.


I'm going to use dummyjson.com to get some data to explain. Imagine you’re building a product page, and you want to fetch product details from an API and update your form fields dynamically. Here’s a simplified HTML:

<div x-data="productForm()" x-init="fetchProduct()">
  <div>
    <p>Title: <span x-text="formFields.title"></span></p>
    <p>Price: £<span x-text="formFields.price"></span></p>
    <p>Rating: <span x-text="formFields.rating"></span></p>
    <p>Stock: <span x-text="formFields.stock"></span></p>
  </div>
</div>

And Alpine.js component

function productForm() {
  return {
    formFields: {
      title: '',
      price: 0,
      rating: 0,
      stock: 0
    },
    async fetchProduct() {
      const response = await fetch('https://dummyjson.com/products/1');
      const json = await response.json();

      // Replace formFields but rebind to Alpine
      this.formFields = json;
    }
  };
}

The API response:

{
  "id": 1,
  "title": "Essence Mascara Lash Princess",
  "price": 9.99,
  "rating": 4.94,
  "stock": 5
}

The result: The values have not changed, and still showing the default values.

Why This Fails

When you reassign this.formFields to the new json object, Alpine.js no longer tracks changes to formFields. This is because Alpine’s reactivity system is based on proxies, and direct reassignment breaks the connection to the original proxy object.

In essence, Alpine.js is still observing the old formFields object, but you’ve replaced it with a completely new object.

The Fix

Instead of directly reassigning the object, update individual properties of formFields so that Alpine’s reactivity remains intact:

async fetchProduct() {
  const response = await fetch('https://dummyjson.com/products/1');
  const json = await response.json();

  // Update individual properties
  this.formFields.title = json.title;
  this.formFields.price = json.price;
  this.formFields.rating = json.rating;
  this.formFields.stock = json.stock;
}

By updating the properties one by one, Alpine.js can detect and respond to the changes properly.

A Cleaner Approach

If you’re working with multiple properties, you can use Object.assign to merge the new data into the existing formFields object:

async fetchProduct() {
  const response = await fetch('https://dummyjson.com/products/1');
  const json = await response.json();

  // Merge new data into formFields
  Object.assign(this.formFields, {
    title: json.title,
    price: json.price,
    rating: json.rating,
    stock: json.stock
  });

  // Or you could do this
  // Object.assign(this.formFields, json);
  // though if the json object contains additional properties that you don’t want in this.formFields, they will also be added.
}

This approach avoids keeps the code clean and maintainable.


Direct reassignment of an object in Alpine.js may seem intuitive, but it can break the reactivity system. By updating individual properties or merging data using Object.assign, you can ensure your state remains reactive and your UI updates as expected. This small adjustment can save you significant debugging time and (for me) frustration in Alpine.js projects.

I’m here, learning and working away. If you liked this content and want to keep me going, consider buying me a coffee. Your support keeps this site running and the coffee brewing! ☕️