# React Native and packages

Every component, API and hook of React Native and React, spliced into scripts with their own names and types, and any other package your app ships.

```tsx title="server/Store.tsx"
import { cs } from "@backtickjs/core";
import { useEffect, useRef } from "@backtickjs/react";
import {
  Animated,
  Linking,
  Platform,
  Pressable,
  Text,
  useWindowDimensions,
  View,
} from "@backtickjs/react-native";
import type { ReactNode } from "react";

// Fades its children in when it appears.
const FadeIn = cs`(props: { children: ReactNode }) => {
  const opacity = $useRef(new $Animated.Value(0)).current;
  $useEffect(() => {
    $Animated
      .timing(opacity, { toValue: 1, duration: 400, useNativeDriver: true })
      .start();
  }, []);
  return <$Animated.View style={{ opacity }}>{props.children}</$Animated.View>;
}`;

type StoreInfo = { name: string; phone: string; address: string };

const StoreCard = cs`(props: StoreInfo) => {
  const { width } = $useWindowDimensions();
  const maps =
    $Platform.OS === "ios"
      ? { label: "Open in Maps", url: "https://maps.apple.com/?q=" }
      : {
          label: "Open in Google Maps",
          url: "https://www.google.com/maps/search/?api=1&query=",
        };

  return (
    <$FadeIn>
      <$View
        style={{
          padding: 24,
          gap: 12,
          flexDirection: width > 600 ? "row" : "column",
        }}
      >
        <$Text style={{ fontSize: 24, fontWeight: "bold" }}>{props.name}</$Text>
        <$Pressable onPress={() => $Linking.openURL("tel:" + props.phone)}>
          <$Text>Call {props.phone}</$Text>
        </$Pressable>
        <$Pressable
          onPress={() =>
            $Linking.openURL(maps.url + encodeURIComponent(props.address))
          }
        >
          <$Text>{maps.label}</$Text>
        </$Pressable>
      </$View>
    </$FadeIn>
  );
}`;

export async function Store() {
  const store: StoreInfo = {
    name: "Backtick Coffee",
    phone: "+15550100",
    address: "1 Main St, Portland",
  };
  return cs`<$StoreCard {...$store} />`;
}
```

`@backtickjs/react-native` and `@backtickjs/react` export every component, API
and hook of React Native and React, with the same names and types. In a
script, splice them with `$`:

- **Components are tags:** `<$View>`, `<$Pressable>`, `<$ScrollView>`.
- **Members are tags too:** `<$Animated.View>` reads `View` off `Animated`.
- **APIs are values:** `$Platform.OS`, `$Linking.openURL(…)`,
  `$StyleSheet.create(…)`.
- **Hooks are called in client components,** as in any React component:
  `$useState`, `$useEffect`, `$useRef`, `$useWindowDimensions`.

The phone runs them with the React Native your app ships, so a screen uses the
same version as the rest of your app.

## The platform is the phone's

`$Platform.OS` and `$useWindowDimensions()` run on the phone, so they describe
the device the screen is drawn on. A server component can't know them; ask in
a client component, as `StoreCard` does.

## Any package your app ships

A screen can use any package your app ships. `createImport` names an export of
a package, typed as the package types it, and splices like anything else:
`$impactAsync()`, `<$LinearGradient>`.

```ts title="server/expo.ts"
import { createImport } from "@backtickjs/core";
import type { impactAsync as ImpactAsync } from "expo-haptics";
import type { LinearGradient as ExpoLinearGradient } from "expo-linear-gradient";

// What the app provides from these packages, typed as the packages type
// them, for any script to splice.
export const impactAsync = createImport<typeof ImpactAsync>({
  name: "impactAsync",
  from: "expo-haptics",
  version: "~57.0.0",
});

export const LinearGradient = createImport<typeof ExpoLinearGradient>({
  name: "LinearGradient",
  from: "expo-linear-gradient",
  version: "~57.0.0",
});
```

A package a screen uses has to be in three places:

**1. The screen,** spliced as any value or component:

```tsx title="server/Home.tsx"
import { cs } from "@backtickjs/core";
import { Pressable, Text } from "@backtickjs/react-native";
import { impactAsync, LinearGradient } from "./expo.js";

export async function Home() {
  return cs`(
    <$LinearGradient
      colors={["#7c3aed", "#0891b2"]}
      style={{ flex: 1, padding: 24 }}
    >
      <$Pressable onPress={() => $impactAsync()}>
        <$Text style={{ color: "#fff", fontSize: 24 }}>Tap to feel it</$Text>
      </$Pressable>
    </$LinearGradient>
  )`;
}
```

**2. Your server,** in the versions the app provides:

```tsx title="server/index.tsx"
import { createServer } from "node:http";
import { createRequire } from "node:module";
import { bundler } from "@backtickjs/bundler";
import { Home } from "./Home.js";

// The packages the app provides to screens, each at the version it's built
// with.
const require = createRequire(import.meta.url);
const packageVersions = {
  react: require("react/package.json").version,
  "react-native": require("react-native/package.json").version,
  "expo-haptics": require("expo-haptics/package.json").version,
  "expo-linear-gradient": require("expo-linear-gradient/package.json").version,
};

createServer(async (request, response) => {
  const bundle = await bundler.build({ input: <Home />, packageVersions });
  response.setHeader("content-type", "text/javascript");
  response.end(bundle.generate({ format: "cjs" }).code);
}).listen(3000);
```

**3. Your app,** in the modules a screen may require, handed to `evaluate`:

```tsx title="App.tsx"
import { evaluate } from "@backtickjs/react-native-client";
import * as ExpoHaptics from "expo-haptics";
import * as ExpoLinearGradient from "expo-linear-gradient";
import * as React from "react";
import * as JSXRuntime from "react/jsx-runtime";
import * as ReactNative from "react-native";

// What a screen may require: the packages this app was built with.
export const modules = {
  react: React,
  "react/jsx-runtime": JSXRuntime,
  "react-native": ReactNative,
  "expo-haptics": ExpoHaptics,
  "expo-linear-gradient": ExpoLinearGradient,
};

export async function fetchScreen(url: string): Promise<React.ReactNode> {
  const response = await fetch(url);
  return evaluate(await response.text(), modules) as React.ReactNode;
}
```

Install the package in your app as usual, `npx expo install expo-haptics`, so
it's built into the app and its native code ships with it.

## Versions are checked

`createImport`'s `version` is the range of the package the export works with.
When a screen is bundled, the bundler checks it against the version your
server says the app provides, and refuses a screen the app can't run, before
anything reaches the phone. [Errors](/docs/errors#cant-import--from--the-client-provides-)
has the messages. Serving apps of several versions, give each its own
`packageVersions`, and each gets a screen it can run.
