最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How to share state among components in Astro? - Stack Overflow

programmeradmin1浏览0评论

I believe I'm taking the wrong approach in my code, how can I set a client side preference in a button click that is used as a prop in all my astro components globally? Or how should I do this? I know it is possible since astro js themselves do this in their docs website! (Explanation of my attempts bellow)

I'm currently working on an Astro Js project, my personal portfolio, and I wanted for it to show the visitors a option to select preferred language, so initially I was passing a language prop in all my astro components. So I tried making a button in the index or the layout file, where all my components receive their props, to change this value, but I quickly realized that is not how it is supposed to be done:

index.astro: (WRONG)

---
let lang = "pt-br";
---
<div id="langBtn">
  <span id="brbtn">BR</span><span>|</span><span id="enbtn">EN</span>
</div>
<Layout title="Daniel Folio" lang={lang}>
  <Hero lang={lang} />
</Layout>

<script>
  function handleClick() {
    if(lang === "pt-br") {
       lang = "en";
    } else {
       lang = "pt-br";
    }
  }
  document
    ?.getElementById("langBtn")
    ?.addEventListener("click", handleClick);
</ script>

I realized we cannot access or change server side variables in client-side. So I tried using Astro cookies to check if a language cookie exist otherwise create one and when the button is clicked change its value, but that's also impossible since Astro.cookies is not available client side.

I believe I'm taking the wrong approach in my code, how can I set a client side preference in a button click that is used as a prop in all my astro components globally? Or how should I do this? I know it is possible since astro js themselves do this in their docs website! (Explanation of my attempts bellow)

I'm currently working on an Astro Js project, my personal portfolio, and I wanted for it to show the visitors a option to select preferred language, so initially I was passing a language prop in all my astro components. So I tried making a button in the index or the layout file, where all my components receive their props, to change this value, but I quickly realized that is not how it is supposed to be done:

index.astro: (WRONG)

---
let lang = "pt-br";
---
<div id="langBtn">
  <span id="brbtn">BR</span><span>|</span><span id="enbtn">EN</span>
</div>
<Layout title="Daniel Folio" lang={lang}>
  <Hero lang={lang} />
</Layout>

<script>
  function handleClick() {
    if(lang === "pt-br") {
       lang = "en";
    } else {
       lang = "pt-br";
    }
  }
  document
    ?.getElementById("langBtn")
    ?.addEventListener("click", handleClick);
</ script>

I realized we cannot access or change server side variables in client-side. So I tried using Astro cookies to check if a language cookie exist otherwise create one and when the button is clicked change its value, but that's also impossible since Astro.cookies is not available client side.

Share Improve this question edited Feb 28, 2024 at 5:54 VLAZ 29k9 gold badges62 silver badges83 bronze badges asked Dec 13, 2022 at 12:00 Daniel GuedesDaniel Guedes 8372 gold badges9 silver badges17 bronze badges
Add a comment  | 

2 Answers 2

Reset to default 16

Introduction

The introduction will focus on the general answer to How to share state among components in Astro? The solution section will focus on how to sync client and server components with cookies in Astro.

Sharing states and events among client components

  • Framework : Front End Frameworks such as Vue, Solid, Preact,... do handle components state sharing
  • Library : for a solution that can be used without a commitment to a given Framework see https://github.com/nanostores/nanostores more details about this in Astro Docs https://docs.astro.build/en/core-concepts/sharing-state/
  • vanilla js (states only) : as simple as module global var (as reminded by user SP33D). note exporting a variable is possible, but exporting a function is a more desirable pattern
const devices = {
  poster:{}
}}
function get_devices(){
  return devices
}
export{
  get_devices
}

full project example https://github.com/MicroWebStacks/astro-home-control/blob/2107f94d0ba9ca76b43777d8ca24eae99b3e78cc/src/libs/power_state.js#L7

Sharing states and events with vanilla js

  • Vanilla js : it is also possible to use custom events between client components where the html element is the target of the event like in this example
...
event(panel,"update",data[name])
...
element.addEventListener("update",(event)=>{
      panel_set_state(name,event.detail)
    })

fulle project example : https://github.com/MicroWebStacks/astro-home-control/blob/2107f94d0ba9ca76b43777d8ca24eae99b3e78cc/src/pages/index.astro#L54

Sharing State between Client and Server

Server to client through page reload

  • by embedding variables directly within the html tags or inside html attributes <p class="value">{init}</p> (simplest and most common)

  • by using <script define:vars={{init}}> This could be part of the solution but it does not handle the client->server back link and comes with disadvantages

    • the passed variable will get hardcoded in the script which becomes specific to the component instance (if you have 10 components, you get 10 scripts each in the body with the corresponding component instance value !!!) This is how it is translated

    • as consequence, the script is inline, no more bundling, late loading (not in a head asset anymore)

    • no caching, fetched in every page,...

Server to client without page reload

For dynamically server changing variables, Server Sent Events can be used https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events

I will not detail it here as it is not needed for this question, but it can be used with Astro and an example can be found here https://github.com/MicroWebStacks/astro-examples#03_sse-counter

Within the same scope websockets can be used as well, but that starts to become over-dimentioned for the problem, if complexity is not an issue then also databases can be accessed from both client and server.

Server and Client persistence and sync

Using client storage

This is a possible solution that client persist his choice in storage, the disadvantages are

  • flicker of server answer in case storage is only known to the client
  • or more code to be developed to handle client side submission and server side endpoint capture, which needs to handle session and user management !!!

for those interested in the advanced example of flicker free state share with sessionStorage and url param here a working sample https://github.com/MicroWebStacks/astro-examples#14_client-storage-counter

Using cookies

This is the simplest way, see solution

Solution : how to sync client and server in Astro using cookies

The question post got the right investigation then got stuck on how to use cookies client side :

here is all it takes to set and get cookies with on the client, in few lines of vanilla javascript, no depndencies. See this question/answer Set cookie and get cookie with JavaScript

here minified and customized for a counter, but for expiry handling, see the referenced question

    function get_counter(){
        const entry = document.cookie.split(';').find(entry=>entry.replace(' ','').startsWith('counter='))
        if(entry){
            return parseInt(entry.split('=')[1])
        }else{
            return 0
        }
    }
    function set_counter(counter){
        document.cookie = `counter=${counter}`
        console.log(`new counter value = ${counter}`)
    }

Example of client side persistent counter

To make the example simpler, I used a counter instead of language here a full example

  • github project : https://github.com/MicroWebStacks/astro-examples#13_client-persistent-counter
  • functional Gitpod : https://gitpod.io/?on=gitpod#https://github.com/MicroWebStacks/astro-examples/tree/main/13_client-persistent-counter

server side cookie usage

in Astro, the counter cookie can be used as follows

let counter = 0
const cookie = Astro.cookies.get("counter")
if(cookie?.value){
    counter = cookie.value
}
console.log(`index.astro> cookie counter = ${counter}`)

Potential issues

  • It is important to ensure that cookies are passing through in the deployment host, as that can be an issue that prevents this solution, it is possible to check that in the example with server console log

If you want to pass this server variable into your component script, you can do so by adding the following code to the script tag:

<script define:vars={{ lang }}>
  console.log(lang);
</script>

However, what you're attempting is to utilize application state. Anything you do inside this Astro component will be limited in scope to the Astro page component's context.

When that script runs on the client, and the lang variable is set, it is only available contextually. No other page component even knows the variable exists.

You can use many stateful solutions with Astro (Such as Redux/React). The Astro docs recommend using Nano stores and preact, which I have found to be a super lightweight solution that works for many small to mid sized projects.

发布评论

评论列表(0)

  1. 暂无评论