Expo Tutorial
This guide walks you through integrating Composify into an Expo project. We assume you already have an Expo project with Expo Router set up. If not, follow the Expo getting started guide first.
Before we begin, make sure your mock API is running on port 9000. (See Prerequisites for setup instructions.) We'll use this API to store and retrieve page content.
1. Install Composify
Install Composify using your preferred package manager:
npm install @composify/react --save2. Your Existing Components
Let's say you have these components in your project:
/* components/Heading.tsx */
import type { FC, PropsWithChildren } from 'react';
import { StyleSheet, Text } from 'react-native';
type Props = PropsWithChildren<{
size?: 'lg' | 'xl' | '2xl' | '3xl' | '4xl' | '5xl';
weight?: 'semibold' | 'bold' | 'extrabold';
align?: 'left' | 'center' | 'right';
}>;
export const Heading: FC<Props> = ({ size = 'lg', weight = 'semibold', align = 'left', children }) => (
<Text
style={[
styles.heading,
styles[`size-${size}`],
styles[`weight-${weight}`],
styles[`align-${align}`]
]}
>
{children}
</Text>
);
const styles = StyleSheet.create({
heading: {
color: '#1E1E1E',
},
'size-lg': {
fontSize: 18,
},
'size-xl': {
fontSize: 20,
},
'size-2xl': {
fontSize: 24,
},
'size-3xl': {
fontSize: 30,
},
'size-4xl': {
fontSize: 36,
},
'size-5xl': {
fontSize: 48,
},
'weight-semibold': {
fontWeight: '600',
},
'weight-bold': {
fontWeight: '700',
},
'weight-extrabold': {
fontWeight: '800',
},
'align-left': {
textAlign: 'left',
},
'align-center': {
textAlign: 'center',
},
'align-right': {
textAlign: 'right',
},
});3. Create the Catalog
Composify needs to know which components it can work with. The Catalog maps your components to the visual editor: prop controls, default values, and categories. This keeps editor metadata separate from your component code.
/* components/catalog.ts */
import { Catalog } from '@composify/react/renderer';
import { Heading } from './Heading';
/* ... */
Catalog.register('Heading', {
component: Heading,
category: 'Content',
props: {
size: {
label: 'Size',
type: 'select',
options: [
{ label: 'Large', value: 'lg' },
{ label: 'Extra Large', value: 'xl' },
{ label: '2XL', value: '2xl' },
{ label: '3XL', value: '3xl' },
{ label: '4XL', value: '4xl' },
{ label: '5XL', value: '5xl' },
],
default: 'lg',
},
weight: {
label: 'Font Weight',
type: 'select',
options: [
{ label: 'Semibold', value: 'semibold' },
{ label: 'Bold', value: 'bold' },
{ label: 'Extrabold', value: 'extrabold' },
],
default: 'semibold',
},
children: {
label: 'Content',
type: 'textarea',
default: 'Heading',
},
},
});
/* ... */4. Render Screens
Now let's create a dynamic route at app/[slug].tsx to render screens. This fetches the layout from the API and passes it to <Renderer />.
import '@/components/catalog';
import { Renderer } from '@composify/react/renderer';
import { useLocalSearchParams } from 'expo-router';
import { useEffect, useState } from 'react';
export default function Page() {
const [source, setSource] = useState<string | null>(null);
const { slug } = useLocalSearchParams<{ slug?: string }>();
useEffect(() => {
const fetchData = async () => {
const res = await fetch(`http://localhost:9000/documents/${slug}`, {
cache: 'no-store',
});
const { content } = await res.json().catch(() => ({}));
setSource(content ?? '<VStack />');
};
fetchData();
}, [slug]);
if (!source) {
return null;
}
return <Renderer source={source} />;
}Make sure to import the components at the top of your page so that catalog registration happens when the page loads.
import '@/components/catalog';
import { Renderer } from '@composify/react/renderer';
import { useLocalSearchParams } from 'expo-router';
import { useEffect, useState } from 'react';
/* ... */http://localhost:8081/foo: displays the saved contenthttp://localhost:8081/baz: shows "Not Found" (no data yet)
5. Visual Editor
Finally, create the editor page at app/editor/[slug].tsx. This is where users can drag, drop, and configure your components.
import '@/components/catalog';
import '@composify/react/style.css';
import { Editor } from '@composify/react/editor';
import { useLocalSearchParams } from 'expo-router';
import { useEffect, useState } from 'react';
import { Alert } from 'react-native';
export default function EditorPage() {
const [source, setSource] = useState<string | null>(null);
const { slug } = useLocalSearchParams<{ slug: string }>();
useEffect(() => {
const fetchData = async () => {
const res = await fetch(`http://localhost:9000/documents/${slug}`, {
cache: 'no-store',
});
const { content } = await res.json().catch(() => ({}));
setSource(content ?? '<VStack size={{ height: 200 }} backgroundColor="#f8fafc" />');
};
fetchData();
}, [slug]);
const handleSubmit = async (source: string) => {
await fetch(`http://localhost:9000/documents/${slug}`, {
method: 'DELETE',
}).catch(() => null);
await fetch('http://localhost:9000/documents', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
id: slug,
content: source,
}),
});
Alert.alert('Saved successfully');
};
if (!source) {
return null;
}
return <Editor title={`Editing: ${slug}`} source={source} onSubmit={handleSubmit} />;
}A couple things to note:
@composify/react/style.cssis required. It contains the editor's core styles.- The catalog import (
@/components/catalog) ensures your components are available in the editor.
Try It Out
- Run your Expo app with
--webflag (editor requires web). - Open
http://localhost:8081/editor/foo, make some changes, and hit Save. - Visit
http://localhost:8081/footo see your changes rendered. - Open
http://localhost:8081/editor/baz, build a new page from scratch, and save it. - Visit
http://localhost:8081/baz. The page that was "Not Found" is now live.
Wrapping Up
That's the basics covered. You now have:
- A document store (json-server for now, swap in a real database for production)
- An editor for visually composing screens with your own components (web only)
- A renderer that turns stored JSX into actual UI (works on iOS, Android, and web)
Next steps:
- Replace json-server with a proper database
- Add authentication
- Deploy so your team can use it
If you don't want to build the backend yourself, Composify Cloud handles storage, version history, and collaboration out of the box.