# Experiment: Astro with Communicating Islands
**Note**: There is an official example [`with-nanostores`](https://github.com/withastro/astro/tree/main/examples/with-nanostores) that lets two islands communicate via [`nanostores`](https://github.com/nanostores/nanostores).
---
**Caution**: At this point it is unclear whether this technique interferes with the islands autonomous hydration process. On the surface this seems to work but it's not clear what potential drawbacks there may be.
The experiment was performed on a "Starter Kit" installation with a Preact renderer.
The experiment has two separate islands (with separate `astro-root`s) running off of the same "store".
```html
---
// file: src/pages/index.astro
import { makeState, bootData } from '../app/counter-store.js';
import Counter from '../components/Counter.jsx';
import DisplayCount from '../components/DisplayCount.jsx';
import BaseLayout from '../layouts/BaseLayout.astro'
import Filler from '../components/Filler.astro';
import PageBootData from '../components/PageBootData.astro';
// Assemble the initial state at build time
// by any means necessary (e.g. fetch)
//
const count = 10;
bootData.initialize(makeState(count));
// Ensure initial state is available
await bootData.dataExport;
---
```
File `index.astro`: This outlines the overall concept. `counter-store` is the entity that both `Counter` and `DisplayCount` are (implicitly) connected to.
- The frontmatter of `index.astro` is responsible for aquiring the necesssary state data at build time. Here `makeState` is used to shape the data for the store, before the store is initialized with it. `bootData.initialize` causes the `bootData.dataExport` promise to resolve to the serialized data.
- This causes the `PageBootData` astro component to render state data into the page.
- Both the `DisplayCount` and `Counter` are Preact components which each has it's own `astro-root` as they are both separated by the `Filler` astro component.
The rendered HTML looks something like
```html
```
## Astro component `PageBootData`
```html
---
// file: src/components/PageBootData.astro
import { bootData } from '../app/counter-store.js';
const data = await bootData.dataExport;
const scriptHtml = ``;
---
{ scriptHtml }
```
- This component simply "awaits" for the serialized initial data to become available. Then it produces a script block to embed the serialized data in the HTML. In the browser this block will be read to initialize the `counter-store` state.
## Preact component `DisplayCount`:
```jsx
// file: src/components/DisplayCount.jsx
//
import { useCounter } from '../app/counter-store.js';
export default function DisplayCount() {
const count = useCounter();
return (
);
}
```
- Simply displays the current `count` found inside of `counter-store`.
## Preact Component `Counter`:
```jsx
// file: src/components/Counter.jsx
//
import { increment, decrement, useCounter } from '../app/counter-store.js';
export default function Counter() {
const count = useCounter();
return (
);
}
```
- In addition to displaying the current `count` also exposes *increment* and *decrement* buttons.
## Module `prime-store`:
```js
// file: src/app/prime-store.js
function hydrateFrom(elementId) {
const element = document.getElementById(elementId);
return JSON.parse(element.text);
}
function makeBootData(elementId, setStore) {
let exportData;
return {
id: elementId,
dataExport: new Promise(resolve => exportData = resolve),
initialize(initialState) {
const serialized = JSON.stringify(initialState);
setStore(initialState);
if (exportData) exportData(serialized);
}
};
}
function primeStore(dataId, initialized, notify) {
let data;
const isBrowser = typeof window === 'object';
const initializeStore = state => {
data = state;
initialized(data);
};
const bootData = isBrowser ? {} : makeBootData(dataId, initializeStore);
if (isBrowser) initializeStore(hydrateFrom(dataId));
return {
update,
bootData
};
function update(transform) {
data = transform(data);
notify(data);
}
}
export {
primeStore
};
```
- `primeStore`
- server side a `bootData` object is created with `makeBootData`. `id` is initialized with `dataId` to identify the script block that holds the relevant serialized state. `dataExport` is a promise that resolves to the serialized state once `initialize` has been invoked with the initial state.
- browser side `hydrateFrom` is used to deserialize the state from the contents of the script block identified by `dataId`
- The returned object holds an `update` function and a `bootData` object (which is empty browser side).
- `update` updates the state with the passed transform (after which `notify` is invoked; `initialized` is invoked instead of `notify` when the store is initialized with its initial state).
## Module `counter-store`:
```js
// file: src/app/counter-store.js
import { useEffect, useState } from 'preact/hooks';
import { primeStore } from './prime-store.js';
// store customizations
//
let initialCount = 0;
const subscribed = new Set();
const DATA_ID = 'page-boot-data';
function initialized(state){
initialCount = state.count;
}
function subscribe(cb) {
subscribed.add(cb);
const unsubscribe = () => subscribed.delete(cb);
return unsubscribe;
}
function notify(state) {
for (const cb of subscribed)
cb(state.count);
}
const store = primeStore(DATA_ID, initialized, notify);
const bootData = store.bootData;
function makeState(count) {
return {
count
};
}
// hook
//
function useCounter() {
const [count, setCount] = useState(initialCount);
useEffect(() => subscribe(
value => setCount(value)
), [setCount]);
return count;
}
const incCount = (state) => (++state.count, state);
const decCount = (state) => (--state.count, state);
const increment = () => store.update(incCount);
const decrement = () => store.update(decCount);
export {
makeState,
bootData,
useCounter,
increment,
decrement,
};
```
- `counter-store` exposes the store prepared by `primeStore` via a `useCounter` hook for Preact components.
- `page-boot-data` is the element ID used to render state server side and hydrate browser side.
- subscribers are notified with the `count` property when the store's `update` is invoked.
- the `initialCount` is cached as a primitive value when the store initializes in order to have a "cheap" initialization value in the `useCounter` hook.
- `makeState` shapes the primitive `count` value into an object to initialize the store.
- `useCounter` will subscribe any component that uses it for updates.
- `increment` and `decrement` "actions" are also made available (for `Counter` component).