All articlesEngineering

A React context does not cross an island boundary

The trap is expensive because it is invisible: while only one island exists, everything works.

1 min read

Every Astro island is its own render root. Two islands on the same page are therefore two distinct trees, mounted separately, sharing nothing.

That is what makes the trap costly: a React context wrapped around an island works perfectly — as long as there is only one. The day a second island needs the same value, it does not receive it, and nothing says so. No error, no warning: just a theme toggle in the footer that ignores the one in the navigation bar.

What does cross

A module. The bundler evaluates it once, so every island importing it gets the same instance. That holds for a nanostores store as much as for a plain exported object.

// theme.ts — a module, so a single instance for the whole page.
import { atom } from "nanostores";

export const $theme = atom<"dark" | "light">("dark");

Each island imports that file and subscribes:

import { $theme } from "./theme";

$theme.subscribe((theme) => {
  document.documentElement.dataset.theme = theme;
});

How we check

By rendering the same toggle twice, in two places on the page, and writing a test that clicks one and asserts the other followed. A property you never exercise is not a property: it is an intention.

Act

Dark themePin the site's appearancesetTheme({theme:"dark"})
Light themePin the site's appearancesetTheme({theme:"light"})
System themeFollow the system preference againsetTheme({theme:"system"})
Switch the site to FrenchFrançaissetLocale({locale:"fr"})
Labs — ProductFilter Labs by typefilterLabs({type:"Produit"})
Labs — PrototypeFilter Labs by typefilterLabs({type:"Prototype"})
Labs — ResearchFilter Labs by typefilterLabs({type:"Recherche"})
Labs — Reset filtersClear every Labs filterfilterLabs({reset:true})
Journal — EngineeringFilter the Journal by categoryfilterJournal({category:"Ingénierie"})
Journal — SecurityFilter the Journal by categoryfilterJournal({category:"Sécurité"})
Journal — Agent-NativeFilter the Journal by categoryfilterJournal({category:"Agent-Native"})
Journal — StudioFilter the Journal by categoryfilterJournal({category:"Studio"})
Journal — Reset filtersClear every Journal filterfilterJournal({reset:true})
Tutorials — BeginnerFilter tutorials by levelfilterTutorials({level:"Débutant"})
Tutorials — IntermediateFilter tutorials by levelfilterTutorials({level:"Intermédiaire"})
Tutorials — AdvancedFilter tutorials by levelfilterTutorials({level:"Avancé"})
Tutorials — Reset filtersClear every tutorials filterfilterTutorials({reset:true})

Services

DiscoveryFrame the product before the first line of code, so the budget builds instead of correcting.openService({service:"discovery"})
Product designAn interface that holds together, carried by a design system your developers can keep alive without us.openService({service:"design"})
Web developmentA fast web application that holds up over time, with the tests and delivery chain to match.openService({service:"web"})
Mobile developmentAn iOS and Android app from a single codebase, published on both stores.openService({service:"mobile"})
Infrastructure & securityInfrastructure described as code, and Zero Trust access that does not rest on a text message.openService({service:"infrastructure"})
Maintenance & evolutionA product kept current, watched and fixed, with someone who answers when it breaks.openService({service:"maintenance"})
Agent-Native Architecture AdvisoryMake an existing product agent-operable without rewriting it — and without opening more than it should.openService({service:"agent-native"})

Go to

YorroProduct engineering studio in Abidjan and Paris. We design, we build, we ship.navigate({page:"home"})
ServicesBuild a product end to end, or advise a team on its agent-native architecture.navigate({page:"services"})
LabsWhat the studio has built: products, prototypes and research, with where each one actually stands.navigate({page:"labs"})
TutorialsStep-by-step guides drawn from real projects, grouped into tracks by topic.navigate({page:"tutorials"})
JournalThe studio's notes and positions.navigate({page:"journal"})
ContactDescribe your project; you get a framing back, not a boilerplate quote.navigate({page:"contact"})
AboutWho runs the studio, how it works, and what it turns down.navigate({page:"about"})
↑↓ move↵ runesc closeOne Actions layer