14

I'm using Alpine to display a list of items that will change. But I can't figure out how to tell Alpine to refresh the list of items once a new one comes back from the server:

<div x-data=" items() ">
    <template x-for=" item in items " :key=" item ">
        <div x-text=" item.name "></div>
    </template>
</div>

The first "batch" of items is fine, because they're hard-coded in the items() function:

    function items(){
        return {
            items: [
                { name: 'aaron'  },
                { name: 'becky'  },
                { name: 'claude' },
                { name: 'david'  }
            ]
        };
    }

Some code outside of Alpine fetches and receives a completely new list of items, that I want to display instead of the original set. I can't figure out how, or if it's even currently possible. Thanks for any pointer.

Mark B
  • 183,023
  • 24
  • 297
  • 295
CWD Subs
  • 191
  • 1
  • 2
  • 7

2 Answers2

38

There are 3 ways to solve this.

  1. Move the fetch into the Alpine.js context so that it can update this.items
  function items(){
        return {
            items: [
                { name: 'aaron'  },
                { name: 'becky'  },
                { name: 'claude' },
                { name: 'david'  }
            ],
            updateItems() {
              // something, likely using fetch('/your-data-url').then((res) => )
              this.items = newItems;
            }
        };
    }
  1. (Not recommended) From your JavaScript code, access rootElement.__x.$data and set __x.$data.items = someValue
<script>
  // some other script on the page
  // using querySelector assumes there's only 1 Alpine component
  document.querySelector('[x-data]').__x.$data.items = [];
</script>
  1. Trigger an event from your JavaScript and listen to it from your Alpine.js component.

Update to the Alpine.js component, note x-on:items-load.window="items = $event.detail.items":

<div x-data=" items() " x-on:items-load.window="items = $event.detail.items">
    <template x-for=" item in items " :key=" item ">
        <div x-text=" item.name "></div>
    </template>
</div>

Code to trigger a custom event, you'll need to fill in the payload.

<script>
let event = new CustomEvent("items-load", {
  detail: {
    items: []
  }
});
window.dispatchEvent(event);
</script>
Hugo
  • 3,500
  • 1
  • 14
  • 28
  • 2
    Thanks Hugo (I've read so many articles on CodeWithHugo before posting here ;) - solution 1 seems easiest, but solution 3 seems the cleanest. I'll try that one. Thanks for replying so quickly! – CWD Subs Aug 03 '20 at 17:20
  • I don't have enough "reputation" to upvote your answer, but thanks again: the custom event approach worked like a charm! – CWD Subs Aug 03 '20 at 21:07
  • 2
    Beautiful, are you able to select this as the answer? – Hugo Aug 04 '20 at 06:33
  • @Hugo Could you clarify why the second option is mentioned as not recommended? I just tried it and saw it works well. In order not to get confused in components, I clarified my selector like this: querySelector('[x-data].myClass') – nektobit Dec 28 '20 at 02:27
  • Option 2 is not recommended because you're accessing Alpine.js internals (`__x` is added by Alpine), which are liable to change with a new major version. – Hugo Dec 29 '20 at 07:08
  • Would there be any reason why option 1 would not update the newly fetched data? I am attempting to do option 1, with a "loading type screen" where the objects are that will be replaced (i am making refresh button) it will refreshes with the new data. Without a loading screen..it takes maybe 10 -15 seconds of "refreshing" function opertation before the page shows the new data (console log says new data should be there). Ideas why alpine isn't updating right away, some sort of caching? – bmiskie May 10 '21 at 06:50
  • @bmiskie I don't think Alpine does any caching – Hugo Jun 03 '21 at 08:00
  • @Hugo In my testing the 3rd option does not work. The dispatched event never gets called. I copied your code directly. – Moss Sep 21 '21 at 08:12
  • @Hugo - I tried option 3 and it only worked when I populated the event payload inline (e.g. I manually added items: [ {"item 1": value....}]) when I tried to populate the payload from a variable that built an array of JSON objects, the alpine table did not update (however the event payload is populated properly). Ever seen anything like that/know a solve? (more detail here - https://stackoverflow.com/questions/74584491/alpine-table-data-not-updated-when-custom-event-payload-is-dynamically-built) – gfppaste Nov 26 '22 at 18:35
5

Expanding on Hugo's great answer I've implemented a simple patch method that lets you update your app's state from the outside while keeping it reactive:

<div x-data="app()" x-on:patch.window="patch">
  <h1 x-text="headline"></h1>
</div>
function app(){
  window.model = {
    headline: "some initial value",
    patch(payloadOrEvent){
      if(payloadOrEvent instanceof CustomEvent){
        for(const key in payloadOrEvent.detail){
          this[key] = payloadOrEvent.detail[key];
        }
      }else{
        window.dispatchEvent(new CustomEvent("patch", {
          detail: payloadOrEvent
        }));
      }
    }
  };
  return window.model;
}

In your other, non-related script you can then call

window.model.patch({headline : 'a new value!'});

or, if you don't want assign alpine's data model to the window, you can simply fire the event, as in Hugo's answer above:

window.dispatchEvent(new CustomEvent("patch", {
  detail: {headline : 'headline directly set by event!'}
}));
oehmaddin
  • 121
  • 3
  • 4