#I'd like to pass a default value to my nanostore state but the data comes from the server and I'm no

13 messages · Page 1 of 1 (latest)

tidal spoke
#

I'd like to pass a default value to my nanostore state but the data comes from the server and I'm not sure how to stream it to client. I've had this problem in a few places now.

// store.ts
const visibleCategories = atom<string[]>([])
// I want to initially populate this arr with all categories
// and not be empty. But the categoreis come from server
---
// index.astro
const categories = await graphQLqueryResult()
---
<ReactComponent items={categories} client:load />

I don't like my current approach where I change the initial value inside useEffect. Code smell.

function ReactComponent({categories}){
   useEffect(()=>{ visibleCategories.set(categories) },[])

   return(
      <div>
         { 
            categories.map(c=>{
               // Causes hydration errors and screen flash
               if (!visibleCategories.includes(c)) return 
               return c
            }) 
         }
      </div>
   )
}
}

This (1) causes a flash where the list is blank and then comes alive and (2) Hydration problem

echo lilyBOT
#

Hi @tidal spoke. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:

#

There isn’t an official “Nano Stores + server data” pattern documented for this exact case, but the docs do describe the key constraints you’re running into:

  • Astro frontmatter runs on the server and is not available in the browser. [Pass variables]
  • Nano Stores are meant for client-side reactivity; using them in server code (frontmatter) is not recommended and won’t affect hydrated components. [Why Nano Stores]

Given that, you can’t “stream” the server value directly into the store from frontmatter. Instead, you need to:

  1. Use the server-fetched data as the single source of truth for initial render (to avoid hydration mismatch and flashing).
  2. Initialize the Nano Store on the client from that same data, but in a way that doesn’t change what was already rendered.

A pattern that stays within what the docs describe is:

1. Pass server data to the client component as props

You’re already doing this correctly:

---
// index.astro
const categories = await graphQLqueryResult();
---
<ReactComponent categories={categories} client:load />

Passing serializable props to a hydrated framework component is the intended way. [Passing props]

2. Render from props, not from the store, on first paint

Use the categories prop for the actual list rendering so that server and client markup match and there’s no flash:

// ReactComponent.tsx
import { useEffect } from 'react';
import { visibleCategories } from '../store';
#

function ReactComponent({ categories }: { categories: string[] }) {
// Initialize the store from props, but don't read from it for initial render
useEffect(() => {
visibleCategories.set(categories);
}, [categories]);

return (
<div>
{categories.map((c) => (
<div key={c}>{c}</div>
))}
</div>
);
}

export default ReactComponent;


Because the rendered list is based only on `categories` (the same data used on the server), there’s no mismatch and no blank state.

### 3. Use the store only for *subsequent* client-only interactions

If you need to hide/show categories later, derive that from the store in event handlers or derived state, but don’t gate the initial render on `visibleCategories`:

```tsx
import { useStore } from '@nanostores/react';
import { visibleCategories } from '../store';

function ReactComponent({ categories }: { categories: string[] }) {
  const $visible = useStore(visibleCategories);

  useEffect(() => {
    visibleCategories.set(categories);
  }, [categories]);

  return (
    <div>
      {categories.map((c) => {
        if (!$visible.includes(c)) return null;
        return <div key={c}>{c}</div>;
      })}
    </div>
  );
}

Because the store is initialized in useEffect to the same value that was already rendered, there’s no visual flash: the first client render sees categories and an empty store, but the DOM already matches the server output (all categories visible). After useEffect runs, the store value changes, but the visible list stays the same unless you later modify the store.

Why not initialize the store on the server?

The Nano Stores docs for Astro explicitly warn:

#
  • Writing to a store from .astro or non-hydrated components will not affect the value received by client-side components.
  • You cannot subscribe to store changes from .astro files. [Why Nano Stores]

So there’s no supported way (in the docs) to “pre-fill” a Nano Store from Astro frontmatter and have that be the initial client value. The recommended pattern is to pass data as props and then use client-side scripts or framework components for reactivity. [Pass variables; Data fetching]

In short: keep the server-fetched categories as the initial render source, and treat the Nano Store as a purely client-side enhancement initialized from those props.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@tidal spoke Kapa.ai is still learning and improving, please let me know how I did by reacting below.

tidal spoke
#

does astro support exporting from script tags?
<script>
export const helloworld = "hello world"
</script>
@echo lily

echo lilyBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

Astro’s docs don’t explicitly mention supporting export statements inside <script> tags in the template (the HTML part of a .astro file), and they describe those scripts as code that runs in the browser, not as modules you import from elsewhere. [Scripts guide]

The documented place to use export is in the component script/frontmatter (between the --- fences), which is standard ESM and can import/export like normal JavaScript or TypeScript. [Component script; Import statements]

So, based on the docs:

  • You can use export in the frontmatter (--- ... ---).
  • There’s no documented support for exporting values from a <script> tag in the template and then importing them elsewhere in Astro. Those <script> tags are meant for client-side behavior, not for sharing exports between modules.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@tidal spoke Kapa.ai is still learning and improving, please let me know how I did by reacting below.

tidal spoke
#

can I pass data from frontmatter to script tag? @echo lily

const categoryArr = await getAllCategoriesQueryResult.categories

<script>
import { visibleCategories } from "@/lib/store"

visibleCategories.set(categoryArr)
</script>

echo lilyBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

Yes, you can pass data from frontmatter to a <script> tag, but not by directly using the frontmatter variable name inside the script like in your example.

Frontmatter runs on the server and is not available in the browser. To get those values into a client script, Astro recommends putting them into HTML (e.g. data-* attributes) and then reading them in the script. [Pass variables]

Your code:

---
const categoryArr = await getAllCategoriesQueryResult.categories
---

<script>
  import { visibleCategories } from "@/lib/store"

  visibleCategories.set(categoryArr)
</script>

won’t work as written, because categoryArr is not defined in the browser context.

Use a data attribute instead:

---
const categoryArr = await getAllCategoriesQueryResult.categories;
const jsonCategories = JSON.stringify(categoryArr);
---

<div id="categories-root" data-categories={jsonCategories}></div>

<script>
  import { visibleCategories } from '@/lib/store';

  const el = document.getElementById('categories-root');
  if (el) {
    const raw = el.dataset.categories;
    if (raw) {
      const categories = JSON.parse(raw);
      visibleCategories.set(categories);
    }
  }
</script>

This follows the documented pattern for passing frontmatter variables to client-side scripts. [Pass variables]

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: