Display Frames in your React Native app
This guide shows you how to use the @frames.js/render
Headless UI to display frames in your React Native app.
Steps
Install the render package to your app
npm install @frames.js/render
Add render proxy for routing frame requests via your backend
This helps to protect privacy of your users and prevent CORS issues.
Please follow the instructions in the Display Frames in React.
Set up the useFrame()
hook
See the useFrame()
hook documentation for more information.
import {
type FarcasterSigner,
signFrameAction,
} from "@frames.js/render/farcaster";
import { useFrame } from "@frames.js/render/use-frame";
import { fallbackFrameContext } from "@frames.js/render";
export default function App() {
// @TODO: replace with your farcaster signer
const farcasterSigner: FarcasterSigner = {
fid: 1,
status: "approved",
publicKey:
"0x00000000000000000000000000000000000000000000000000000000000000000",
privateKey:
"0x00000000000000000000000000000000000000000000000000000000000000000",
};
const frameState = useFrame({
// replace with frame URL
homeframeUrl: "https://framesjs.org",
// corresponds to the name of the route for POST and GET in step 2
frameActionProxy: "/frames",
frameGetProxy: "/frames",
connectedAddress: undefined,
frameContext: fallbackFrameContext,
// map to your identity if you have one
signerState: {
hasSigner: farcasterSigner.status === "approved",
signer: farcasterSigner,
isLoadingSigner: false,
async onSignerlessFramePress() {
// Only run if `hasSigner` is set to `false`
// This is a good place to throw an error or prompt the user to login
console.log(
"A frame button was pressed without a signer. Perhaps you want to prompt a login"
);
},
signFrameAction,
async logout() {
// here you can add your logout logic
console.log("logout");
},
},
});
// @TODO here will go the renderer
return null;
}
Add the renderer
See the Headless UI documentation for more information about the structure, components and theme.
import {
type FarcasterSigner,
signFrameAction,
} from "@frames.js/render/farcaster";
import { useFrame } from "@frames.js/render/use-frame";
import { fallbackFrameContext } from "@frames.js/render";
import {
FrameUI,
type FrameUIComponents,
type FrameUITheme,
} from "@frames.js/render/ui";
import type { ImageStyle, TextStyle, ViewStyle } from "react-native";
import { View } from "react-native";
/**
* StylingProps is a type that defines the props that can be passed to the components to style them.
*/
type StylingProps = {
style?: ImageStyle | TextStyle | ViewStyle;
};
/**
* You can override components to change their internal logic or structure if you want.
* By default it is not necessary to do that since the default structure is already there
* so you can just pass an empty object and use theme to style the components.
*
* You can also style components here and completely ignore theme if you wish.
*/
const components: FrameUIComponents<StylingProps> = {
ImageContainer(props, stylingProps) {
return (
<View
style={{
// helps loading screen to stretch and also provides a minimal space when frame is loading
aspectRatio: props.aspectRatio,
}}
>
{props.image}
{props.messageTooltip}
</View>
);
},
};
/**
* By default there are no styles so it is up to you to style the components as you wish.
*/
const theme: FrameUITheme<StylingProps> = {
Root: {
style: {
position: "relative",
} satisfies ViewStyle,
},
LoadingScreen: {
style: {
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: "gray",
zIndex: 10,
} satisfies ViewStyle,
},
};
export default function App() {
// @TODO: replace with your farcaster signer
const farcasterSigner: FarcasterSigner = {
fid: 1,
status: "approved",
publicKey:
"0x00000000000000000000000000000000000000000000000000000000000000000",
privateKey:
"0x00000000000000000000000000000000000000000000000000000000000000000",
};
const frameState = useFrame({
// replace with frame URL
homeframeUrl: "https://framesjs.org",
// corresponds to the name of the route for POST and GET in step 2
frameActionProxy: "/frames",
frameGetProxy: "/frames",
connectedAddress: undefined,
frameContext: fallbackFrameContext,
// map to your identity if you have one
signerState: {
hasSigner: farcasterSigner.status === "approved",
signer: farcasterSigner,
isLoadingSigner: false,
async onSignerlessFramePress() {
// Only run if `hasSigner` is set to `false`
// This is a good place to throw an error or prompt the user to login
console.log(
"A frame button was pressed without a signer. Perhaps you want to prompt a login"
);
},
signFrameAction,
async logout() {
// here you can add your logout logic
console.log("logout");
},
},
});
return (
<FrameUI frameState={frameState} components={components} theme={theme} />
);
}
Done! 🎉
Native Wind Support
If you are using Native Wind in your React Native app, you can use the @frames.js/render
package to render frames in your app with few simple changes.
import {
type FarcasterSigner,
signFrameAction,
} from "@frames.js/render/farcaster";
import { useFrame } from "@frames.js/render/use-frame";
import { fallbackFrameContext } from "@frames.js/render";
import {
FrameUI,
type FrameUIComponents,
type FrameUITheme,
} from "@frames.js/render/ui";
import type { ImageStyle, TextStyle, ViewStyle } from "react-native";
import { View } from "react-native";
/**
* NativeWind uses their own implementation of createElement() to style native components by className prop.
*
* This is already a dependency of nativewind
*/
import { createInteropElement } from "react-native-css-interop";
/**
* StylingProps is a type that defines the props that can be passed to the components to style them.
*/
type StylingProps = {
className?: string;
style?: ImageStyle | TextStyle | ViewStyle;
};
/**
* You can override components to change their internal logic or structure if you want.
* By default it is not necessary to do that since the default structure is already there
* so you can just pass an empty object and use theme to style the components.
*
* You can also style components here and completely ignore theme if you wish.
*/
const components: FrameUIComponents<StylingProps> = {
ImageContainer(props, stylingProps) {
return (
<View
style={{
// helps loading screen to stretch and also provides a minimal space when frame is loading
aspectRatio: props.aspectRatio,
}}
>
{props.image}
{props.messageTooltip}
</View>
);
},
};
/**
* By default there are no styles so it is up to you to style the components as you wish.
*/
const theme: FrameUITheme<StylingProps> = {
Root: {
className: "relative",
style: {
position: "relative",
} satisfies ViewStyle,
},
LoadingScreen: {
className: "absolute top-0 left-0 right-0 bottom-0 bg-gray-300 z-10",
style: {
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: "gray",
zIndex: 10,
} satisfies ViewStyle,
},
};
export default function App() {
// @TODO: replace with your farcaster signer
const farcasterSigner: FarcasterSigner = {
fid: 1,
status: "approved",
publicKey:
"0x00000000000000000000000000000000000000000000000000000000000000000",
privateKey:
"0x00000000000000000000000000000000000000000000000000000000000000000",
};
const frameState = useFrame({
// replace with frame URL
homeframeUrl: "https://framesjs.org",
// corresponds to the name of the route for POST and GET in step 2
frameActionProxy: "/frames",
frameGetProxy: "/frames",
connectedAddress: undefined,
frameContext: fallbackFrameContext,
// map to your identity if you have one
signerState: {
hasSigner: farcasterSigner.status === "approved",
signer: farcasterSigner,
isLoadingSigner: false,
async onSignerlessFramePress() {
// Only run if `hasSigner` is set to `false`
// This is a good place to throw an error or prompt the user to login
console.log(
"A frame button was pressed without a signer. Perhaps you want to prompt a login"
);
},
signFrameAction,
async logout() {
// here you can add your logout logic
console.log("logout");
},
},
});
return (
<FrameUI
frameState={frameState}
components={components}
theme={theme}
createElement={
createInteropElement as unknown as typeof React.createElement
}
/>
);
}
Proxying images via your backend
It is recommended to proxy images via your backend to preserve user privacy and prevent CORS issues.
See the Display Frames in React guide for more information on how to transform frame image URLs before rendering them in your app.