Skip to content

Instantly share code, notes, and snippets.

@CodyJasonBennett
Created May 16, 2022 17:27
Show Gist options
  • Save CodyJasonBennett/335664f5f21b174e1c824649152ea4cc to your computer and use it in GitHub Desktop.
Save CodyJasonBennett/335664f5f21b174e1c824649152ea4cc to your computer and use it in GitHub Desktop.
v7 React Native Canvas
/**
* react-native example using R3F v7.
*
* Dependencies:
* - @react-three/fiber 7.0.29
* - react 17.0.1
* - react-native 0.68.2
* - expo-gl 11.3.0
*/
import * as React from 'react'
import { render, unmountComponentAtNode } from '@react-three/fiber'
import { PixelRatio, View, StyleSheet } from 'react-native'
import { GLView } from 'expo-gl'
function Block({ set }) {
React.useLayoutEffect(() => {
set(new Promise(() => null))
return () => set(false)
}, [set])
return null
}
class ErrorBoundary extends React.Component {
state = { error: false }
static getDerivedStateFromError = () => ({ error: true })
componentDidCatch(error) {
this.props.set(error)
}
render() {
return this.state.error ? null : this.props.children
}
}
const Canvas = React.forwardRef(function Canvas({ style, children, ...props }, forwardedRef) {
const viewRef = React.useRef()
const [size, setSize] = React.useState({ width: 0, height: 0 })
const [canvas, setCanvas] = React.useState(null)
React.useImperativeHandle(forwardedRef, () => viewRef.current)
const [block, setBlock] = React.useState(false)
const [error, setError] = React.useState(false)
// Suspend this component if block is a promise (2nd run)
if (block) throw block
// Throw exception outwards if anything within canvas throws
if (error) throw error
const onLayout = React.useCallback((e) => {
const { width, height } = e.nativeEvent.layout
setSize({ width, height })
}, [])
const onContextCreate = React.useCallback((context) => {
const canvasShim = {
width: context.drawingBufferWidth,
height: context.drawingBufferHeight,
style: {},
addEventListener: () => {},
removeEventListener: () => {},
clientHeight: context.drawingBufferHeight,
getContext: () => context,
}
setCanvas(canvasShim)
}, [])
if (size.width > 0 && size.height > 0 && canvas) {
render(
<ErrorBoundary set={setError}>
<React.Suspense fallback={<Block set={setBlock} />}>{children}</React.Suspense>
</ErrorBoundary>,
canvas,
{
...props,
size,
dpr: PixelRatio.get(),
onCreated(state) {
// Swap buffers on render
const context = state.gl.getContext()
const renderFrame = state.gl.render.bind(state.gl)
state.gl.render = (scene, camera) => {
renderFrame(scene, camera)
context.endFrameEXP()
}
return props?.onCreated?.(state)
},
},
)
}
React.useEffect(() => {
if (canvas) {
return () => unmountComponentAtNode(canvas)
}
}, [canvas])
return (
<View ref={viewRef} onLayout={onLayout} style={{ flex: 1, ...style }}>
{size.width > 0 && size.height > 0 && (
<GLView onContextCreate={onContextCreate} style={StyleSheet.absoluteFill} />
)}
</View>
)
})
export default function App() {
return (
<Canvas camera={{ position: [0, 1.3, 3] }}>
<gridHelper args={[1, 1]} />
</Canvas>
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment