#components and interactive updates

27 messages Β· Page 1 of 1 (latest)

spiral vale
#

I am struggling to implement the following:

I want a component that shows a value (from an API).
This value is read once during a static build and baked into the HTML.
Now when the website is deployed and javascript is enabled I want to show a button.
If the button is pressed it should again read (from an API) and update the value that was baked in during the static build.

I've been been digging into components, island and client loading and did not quite get there yet.
This should be possible, should it?

I am running in hybrid mode as I also have other API endpoints.

terse isle
#

You can use a normal JS script that calls the API when the button is clicked to update the element or wherever the value (from build) was used

spiral vale
#

Let's say I have a component src/components/Counter.astro:

---
const countFromAPI = await getCountFromAPI(); // GET src/pages/api/count.js
---

<script is:inline>
function loadCounterAgain() {
    alert("reloaded")
}
</script>

<button onclick={loadCounterAgain()}>[{countFromAPI}]</button>

Doing it completely in frontend js is one thing - but the duality of both leaves me puzzled.

And how can I reference the endpoint in src/pages/api/count.js when doing it completely from normal JS?

And why is the is:inline required for this even work?

solid garden
#

You shouldn't need is:inline for this tbh! But it's probably because without it, your script will load async after the html has loaded and since you're using onclick in your element rather than attaching a listener in your script, loadCounterAgain isn't defined yet.

But either way, you can just use a fetch to fetch the data from the API on the front-end

// MyComponent.astro
---
const countFromAPI = await getCountFromAPI(); // GET src/pages/api/count.js
---

<script>
const myButton = document.querySelector('button')

const loadCounterAgain = async () => {
    const response = await fetch('http://localhost:4321/api/count', {
  method: 'GET',
})

  const count = await repsonse.body() // or whatever your response is

  myButton.textContent = count
}

myButton.addEventListener('click', () => {
  loadCounterAgain()
})
</script>

<button>[{countFromAPI}]</button>
spiral vale
#

Thanks!

This raises a few more questions though.

http://localhost:4321/api/count isn't likely to work in production. Is there a better way to refer to it?

And with document.querySelector('button') does that apply just to the component? or the full page?

Would it be better to turn this into Alpine/Svelte/... island/component?

The distinction of Astro component and framework component isn't quite clear to me yet.

solid garden
#

Ah sorry yeah I was just sort of psuedocoding it.

As for your API call, yes you'll want to probably figure out what env you're on in prod. Or even just use a root level url: fetch('/api/count') would probably be the easiest thing to do. As your API is also at the same host

And yes again with the querySelector, you could add an ID or class to your button and be more specific with your selector

<button id='my-button'>Click</button>

<script>
  const myButton = document.getElementById('my-button')
  // etc
</script>
#

So an Astro component will always boil down to raw html/js/css. But they can include framework components which will get loaded and rendered on the client at run time.

It's personal preference honestly which you use. If you're more comfortable using a framework component, then absolutely go for it.

But for simple things like this (though I appreciate if the actual use case is more complex) I'd just use vanilla JS and not have the overhead of a framework

spiral vale
#

So while I have this working now ... but how can I bring data from the header into the script?

---
const foo = 1
---

<script>
console.log(foo)
</script>
solid garden
spiral vale
#

So for the safe way I would have to use extends HTMLElement { to get this ... does that make it an island yet? or is that just using a standard webc?

I think in this case the define:vars might be OK. It does work at least πŸ™‚

Much appreciate the help!

solid garden
#

No that will just be a regular web component. And yeah I don't know what you're passing but define:vars is honestly fine most of the time. At least I've not found any problems, but I know it can introduce problems of leaking private server side data to your frontend

spiral vale
#

Yeah, it literally is just an integer/count.

#

But there is another aspect I was just thinking about:

<button data-foo>foo</button>

<script>

  async function update(button) {
    button.innerHTML = `updated`
  }

  const buttons = document.querySelectorAll('[data-foo]')

  buttons.forEach((button) => {
    button.addEventListener('click', (event) => update(button));
  })

</script>

If there are multiple of these components on one page - I will have to manually create an id/data mapping. Correct?

#

Or is there some kind of magic that helps with the scoping?

solid garden
#

No there's no magical scoping unfortunately! Yeah if you want to reference individual guys you'll need to create unique ids or something. Or as you've done there grab all of whatever you want.

#

Also on that, even if you use that component multiple times, it'll only run that script once

spiral vale
spiral vale
#

Is there a way to share a function between the server and the client?
I'd prefer to have a single place to format string for the button.innerHTML = 'updated'

spiral vale
#

Figured it all out. Thanks for the help!

solid garden
#

Ah sorry mate, missed this.

No you can't share a function from the server to the client atm, it has to be serializable

spiral vale
#

I read somewhere that I could just import it from withint the script tag - but that didn't fly.
I ended up changing things to compleltey rid of the need. In a way that's even better πŸ™‚

But it would still be awesome if I could import the same code on client and server.

solid garden
#

I mean you can import on both, just not send functions from the server to the client. Don't know if that's being fictitious πŸ˜‚

spiral vale
#

Importing on both I tried - and failed.

solid garden
#

Want to share what you were trying? πŸ‘€

spiral vale
#

Somewhere I found something like this:

---
// MyComponent.astro
import foo from './some/file'
// more code here
---

<script>
import foo from './some/file'
// more code here
</script>

...and I was surprised this should be working.

And then it didn't πŸ™‚

solid garden
#

As long as it's just generic js, as in not using browser or node apis, that should definitely work

spiral vale
#

Odd