Skip to content

Commit

Permalink
Merge pull request #65 from Shopify/fix/gotchas
Browse files Browse the repository at this point in the history
💄 Add useContextBridge()
  • Loading branch information
chrfalch authored Jan 3, 2022
2 parents f859d14 + 1049ef4 commit b3aa302
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 2 deletions.
26 changes: 26 additions & 0 deletions docs/docs/getting-started/Theme.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { ReactNode } from "react";
import React, { useContext, createContext } from "react";

interface Theme {
primary: string;
}

export const ThemeContext = createContext<Theme | null>(null);

export const ThemeProvider = ({
primary,
children,
}: {
primary: string;
children: ReactNode;
}) => (
<ThemeContext.Provider value={{ primary }}>{children}</ThemeContext.Provider>
);

export const useTheme = () => {
const theme = useContext(ThemeContext);
if (theme === null) {
throw new Error("Theme provider not found");
}
return theme;
};
112 changes: 112 additions & 0 deletions docs/docs/getting-started/contexts.md
Original file line number Diff line number Diff line change
@@ -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 `<Canvas>` 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 <Fill color={primary} />;
};

export const Layer = () => {
const theme = useTheme();
return (
<Canvas style={{ flex: 1 }}>
{/* We need to re-inject the context provider here. */}
<ThemeProvider primary={theme.primary}>
<MyDrawing />
</ThemeProvider>
</Canvas>
);
};

export const App = () => {
return (
<ThemeProvider primary="red">
<Layer />
</ThemeProvider>
);
};
```

## 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 <Fill color={primary} />;
};

export const Layer = () => {
const ContextBridge = useContextBridge(ThemeContext);
return (
<Canvas style={{ flex: 1 }}>
<ContextBridge>
<Fill color="black" />
<MyDrawing />
</ContextBridge>
</Canvas>
);
};

export const App = () => {
return (
<ThemeProvider primary="red">
<Layer />
</ThemeProvider>
);
};
```

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<Theme | null>(null);

export const ThemeProvider = ({
primary,
children,
}: {
primary: string;
children: ReactNode;
}) => (
<ThemeContext.Provider value={{ primary }}>
{children}
</ThemeContext.Provider>
);

export const useTheme = () => {
const theme = useContext(ThemeContext);
if (theme === null) {
throw new Error("Theme provider not found");
}
return theme;
};
```
6 changes: 5 additions & 1 deletion docs/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
28 changes: 27 additions & 1 deletion package/src/renderer/Canvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand All @@ -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<any>[]) => {
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) => (
<Context.Provider value={values[i]} children={acc} />
),
children
) as ReactElement,
[contexts, values]
);
};

export const skiaReconciler = ReactReconciler(skHostConfig);

skiaReconciler.injectIntoDevTools({
Expand Down

0 comments on commit b3aa302

Please sign in to comment.