Minimal atomic state with Nanostores for Google Antigravity projects including stores, computed values, and framework integration.
# Nanostores Atomic State for Google Antigravity
Implement minimal atomic state management with Nanostores in your Google Antigravity IDE projects. This comprehensive guide covers store creation, computed values, and framework integration optimized for Gemini 3 agentic development.
## Store Creation
Create atomic stores with Nanostores:
```typescript
// stores/prompts.ts
import { atom, map, computed } from 'nanostores';
// Primitive atom
export const $searchQuery = atom('');
export const $selectedTag = atom<string | null>(null);
export const $isLoading = atom(false);
// Map store for complex objects
interface Prompt {
id: string;
title: string;
description: string;
tags: string[];
starred: boolean;
}
export const $prompts = map<Record<string, Prompt>>({});
// Computed values
export const $promptList = computed($prompts, (prompts) =>
Object.values(prompts)
);
export const $filteredPrompts = computed(
[$promptList, $searchQuery, $selectedTag],
(prompts, search, tag) => {
return prompts.filter((prompt) => {
const matchesSearch = !search ||
prompt.title.toLowerCase().includes(search.toLowerCase()) ||
prompt.description.toLowerCase().includes(search.toLowerCase());
const matchesTag = !tag || prompt.tags.includes(tag);
return matchesSearch && matchesTag;
});
}
);
export const $starredPrompts = computed($promptList, (prompts) =>
prompts.filter((p) => p.starred)
);
export const $stats = computed([$promptList, $starredPrompts], (all, starred) => ({
total: all.length,
starred: starred.length,
}));
```
## Store Actions
Define actions for store mutations:
```typescript
// stores/actions.ts
import { $prompts, $isLoading, $searchQuery } from './prompts';
export async function fetchPrompts() {
$isLoading.set(true);
try {
const response = await fetch('/api/prompts');
const data = await response.json();
const promptsMap: Record<string, Prompt> = {};
data.prompts.forEach((prompt: Prompt) => {
promptsMap[prompt.id] = prompt;
});
$prompts.set(promptsMap);
} catch (error) {
console.error('Failed to fetch prompts:', error);
} finally {
$isLoading.set(false);
}
}
export function addPrompt(prompt: Prompt) {
$prompts.setKey(prompt.id, prompt);
}
export function updatePrompt(id: string, updates: Partial<Prompt>) {
const current = $prompts.get()[id];
if (current) {
$prompts.setKey(id, { ...current, ...updates });
}
}
export function toggleStar(id: string) {
const current = $prompts.get()[id];
if (current) {
$prompts.setKey(id, { ...current, starred: !current.starred });
}
}
export function removePrompt(id: string) {
const prompts = { ...$prompts.get() };
delete prompts[id];
$prompts.set(prompts);
}
export function setSearch(query: string) {
$searchQuery.set(query);
}
```
## React Integration
Use Nanostores in React components:
```typescript
// components/PromptList.tsx
'use client';
import { useStore } from '@nanostores/react';
import { useEffect } from 'react';
import {
$filteredPrompts,
$isLoading,
$stats,
$searchQuery,
} from '@/stores/prompts';
import { fetchPrompts, toggleStar, setSearch } from '@/stores/actions';
export function PromptList() {
const prompts = useStore($filteredPrompts);
const isLoading = useStore($isLoading);
const stats = useStore($stats);
const search = useStore($searchQuery);
useEffect(() => {
fetchPrompts();
}, []);
if (isLoading) {
return <LoadingSkeleton />;
}
return (
<div>
<div className="stats">
<span>Total: {stats.total}</span>
<span>Starred: {stats.starred}</span>
</div>
<input
type="search"
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="Search prompts..."
/>
<div className="grid">
{prompts.map((prompt) => (
<PromptCard
key={prompt.id}
prompt={prompt}
onStar={() => toggleStar(prompt.id)}
/>
))}
</div>
</div>
);
}
```
## Persistent Storage
Add persistence with localStorage:
```typescript
// stores/persistent.ts
import { persistentAtom, persistentMap } from '@nanostores/persistent';
// Persisted atom
export const $starredIds = persistentAtom<string[]>(
'starred-prompts',
[],
{
encode: JSON.stringify,
decode: JSON.parse,
}
);
// Persisted map
export const $userPreferences = persistentMap<{
theme: 'light' | 'dark';
sortBy: 'date' | 'stars' | 'views';
showStarred: boolean;
}>(
'user-preferences',
{
theme: 'dark',
sortBy: 'date',
showStarred: false,
}
);
// Actions for persistent stores
export function toggleStarredId(id: string) {
const current = $starredIds.get();
if (current.includes(id)) {
$starredIds.set(current.filter((i) => i !== id));
} else {
$starredIds.set([...current, id]);
}
}
export function setTheme(theme: 'light' | 'dark') {
$userPreferences.setKey('theme', theme);
}
export function setSortBy(sortBy: 'date' | 'stars' | 'views') {
$userPreferences.setKey('sortBy', sortBy);
}
```
## Async Tasks
Handle async operations with tasks:
```typescript
// stores/tasks.ts
import { task } from 'nanostores';
// Async task store
export const $fetchPromptTask = task(async (slug: string) => {
const response = await fetch(`/api/prompts/${slug}`);
if (!response.ok) {
throw new Error('Prompt not found');
}
return response.json();
});
// Usage in component
function PromptDetail({ slug }: { slug: string }) {
const taskState = useStore($fetchPromptTask);
useEffect(() => {
$fetchPromptTask.run(slug);
}, [slug]);
if (taskState.loading) {
return <LoadingSkeleton />;
}
if (taskState.error) {
return <ErrorMessage error={taskState.error} />;
}
return <PromptContent prompt={taskState.data} />;
}
```
## Best Practices
1. **Keep stores small** and focused
2. **Use computed** for derived values
3. **Separate actions** from store definitions
4. **Use persistentAtom** for localStorage
5. **Leverage task** for async operations
6. **Use map** for keyed collections
7. **Subscribe carefully** to avoid memory leaksThis nanostores prompt is ideal for developers working on:
By using this prompt, you can save hours of manual coding and ensure best practices are followed from the start. It's particularly valuable for teams looking to maintain consistency across their nanostores implementations.
Yes! All prompts on Antigravity AI Directory are free to use for both personal and commercial projects. No attribution required, though it's always appreciated.
This prompt works excellently with Claude, ChatGPT, Cursor, GitHub Copilot, and other modern AI coding assistants. For best results, use models with large context windows.
You can modify the prompt by adding specific requirements, constraints, or preferences. For nanostores projects, consider mentioning your framework version, coding style, and any specific libraries you're using.