diff --git a/docs/docs/getting-started/Theme.tsx b/docs/docs/getting-started/Theme.tsx new file mode 100644 index 0000000000..cccb4d6e4a --- /dev/null +++ b/docs/docs/getting-started/Theme.tsx @@ -0,0 +1,26 @@ +import type { ReactNode } from "react"; +import React, { useContext, createContext } from "react"; + +interface Theme { + primary: string; +} + +export const ThemeContext = createContext(null); + +export const ThemeProvider = ({ + primary, + children, +}: { + primary: string; + children: ReactNode; +}) => ( + {children} +); + +export const useTheme = () => { + const theme = useContext(ThemeContext); + if (theme === null) { + throw new Error("Theme provider not found"); + } + return theme; +}; diff --git a/docs/docs/getting-started/contexts.md b/docs/docs/getting-started/contexts.md new file mode 100644 index 0000000000..5c37241e5b --- /dev/null +++ b/docs/docs/getting-started/contexts.md @@ -0,0 +1,112 @@ +--- +id: contexts +title: Contexts +sidebar_label: Contexts +slug: /getting-started/contexts +--- + +React Native Skia is using its own React renderer. +Currently, it is not possible to automatically share a React context between two renderers. +This means that a React Native context won't be available from your drawing directly. +We recommend that you prepare the data needed for your drawing outside the `` element. +However, if you need to use a React context within your drawing, you will need to re-inject it. +This module provides the `useContextBridge` hook from [pmndrs/drei](https://github.com/pmndrs/drei#usecontextbridge) to help you bridge between contexts. + +## Manual Context Injection + +```tsx twoslash +import React from "react"; +import { Canvas, Fill } from "@shopify/react-native-skia"; +import {useTheme, ThemeProvider} from "./docs/getting-started/Theme"; + +const MyDrawing = () => { + const { primary } = useTheme(); + return ; +}; + +export const Layer = () => { + const theme = useTheme(); + return ( + + {/* We need to re-inject the context provider here. */} + + + + + ); +}; + +export const App = () => { + return ( + + + + ); +}; +``` + +## Using `useContextBridge()` + +```tsx twoslash +import React from "react"; +import { useContextBridge, Canvas, Fill } from "@shopify/react-native-skia"; +import {useTheme, ThemeProvider, ThemeContext} from "./docs/getting-started/Theme"; + +const MyDrawing = () => { + const { primary } = useTheme(); + return ; +}; + +export const Layer = () => { + const ContextBridge = useContextBridge(ThemeContext); + return ( + + + + + + + ); +}; + +export const App = () => { + return ( + + + + ); +}; +``` + +Below is the context definition that was used in this example: + +```tsx twoslash +import type { ReactNode } from "react"; +import React, { useContext, createContext } from "react"; + +interface Theme { + primary: string; +} + +export const ThemeContext = createContext(null); + +export const ThemeProvider = ({ + primary, + children, +}: { + primary: string; + children: ReactNode; +}) => ( + + {children} + +); + +export const useTheme = () => { + const theme = useContext(ThemeContext); + if (theme === null) { + throw new Error("Theme provider not found"); + } + return theme; +}; +``` diff --git a/docs/sidebars.js b/docs/sidebars.js index f7f50df15b..662ab81604 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -18,7 +18,11 @@ const sidebars = { collapsed: false, type: "category", label: "Getting started", - items: ["getting-started/installation", "getting-started/hello-world"], + items: [ + "getting-started/installation", + "getting-started/hello-world", + "getting-started/contexts", + ], }, { collapsed: false, diff --git a/package/src/renderer/Canvas.tsx b/package/src/renderer/Canvas.tsx index 6a5d3f9bf1..5784420d74 100644 --- a/package/src/renderer/Canvas.tsx +++ b/package/src/renderer/Canvas.tsx @@ -4,8 +4,15 @@ import React, { useState, useCallback, useMemo, + useContext, +} from "react"; +import type { + RefObject, + ReactNode, + ComponentProps, + Context, + ReactElement, } from "react"; -import type { RefObject, ReactNode, ComponentProps } from "react"; import type { OpaqueRoot } from "react-reconciler"; import ReactReconciler from "react-reconciler"; @@ -19,6 +26,25 @@ import { CanvasNode } from "./nodes/Canvas"; import { vec } from "./processors"; import { popDrawingContext, pushDrawingContext } from "./CanvasProvider"; +// useContextBridge() is taken from https://github.com/pmndrs/drei#usecontextbridge +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const useContextBridge = (...contexts: Context[]) => { + const values = + // eslint-disable-next-line react-hooks/rules-of-hooks + contexts.map((context) => useContext(context)); + return useMemo( + () => + ({ children }: { children: ReactNode }) => + contexts.reduceRight( + (acc, Context, i) => ( + + ), + children + ) as ReactElement, + [contexts, values] + ); +}; + export const skiaReconciler = ReactReconciler(skHostConfig); skiaReconciler.injectIntoDevTools({