React Native

Rendering Responses

The curiProvider() function is used to create the component at the root of a Curi + React application. You can call this component anything that you want, but here it will be referred to as the <Router>.

Note:

Why does @curi/react-native export a function to create a component and not just a component? Props signify values that can change, but an application should only ever have one router. By hard-coding the router into a component, we avoid having to handle the possibility of switching routers (which should not happen).

curiProvider() is passed the application's Curi router. The returned component will automatically add an observer to the Curi router when it mounts, so that it can re-render when there are new responses.

The <Router> takes a render-invoked function as its children prop. This function will be called with an object that has three properties— response, router, and navigation—and returns the React element(s) that form the root of the application.

import { curiProvider } from '@curi/react-native';

import router from "./router";
const Router = curiProvider(router);

const App = () => (
  <Router>
    {({ response, router, navigation }) => {
      return <response.body />;
    }}
  </Router>
);

What to return from children()

The render-invoked children() is responsible for rendering the root elements for an application.

Unlike with the DOM, React Native cannot have its initial render delayed with a router.once() call. Instead, the children() function should check if the response exists, and rendering a loading component when it does not.

If you set React components as the body properties on your responses, you can create a React element for the body component in this function.

The <Body> element (it is useful to rename the response's body to Body for JSX transformation) is a placeholder for the "real" component that you render for a route. This means that the "real" component will be different for every route. When it comes to passing props to the <Body>, you could use response.name to determine what props to pass based on which route matched, but passing the same props to every route's <Body> is usually sufficient. Passing the entire response is generally useful so that the route components can access any params, data, and other properties of the response.

const App = () => (
  <Router>
    {({ response, router, navigation }) => {
      // async route protection
      if (!response) {
        return <Loading />;
      }

      // rename body to Body for JSX transformation
      const { body:Body } = response;
      return (
        <React.Fragment>
          <NavLinks />
          <Body response={response} />
        </React.Fragment>
      );
    }}
  </Router>
);

If your routes use an object to attach multiple components to a response, the children() function also provides a good place to split these apart.

If you do take this approach, please remember that you want every route to set the same body shape. Otherwise, you'll have to determine the shape and change how you render in the children() function, which can quickly become messy.

const routes = prepareRoutes([
  {
    name: "Home",
    path: "",
    response() {
      return {
        body: {
          Main: HomeMain,
          Menu: HomeMenu
        }
      }
    }
  },
  // ...
]);

const App = () => (
  <Router>
    {({ response, router, navigation }) => {
      const { Main, Menu } = response.body;
      return (
        <React.Fragment>
          <Menu />
          <Main response={response} />
        </React.Fragment>
      );
    }}
  </Router>
);
Note:

There is a <Curious> component that you can render to access the response, router, and navigation objects anywhere* in your application. This can help prevent having to pass props through multiple layers of components.

* anywhere that is a child of your <Router>.

import { Curious } from "@curi/react-native";
            
const BaseRouteName = ({ response }) => (
  <Text>{response.name}</Text>
);

export default function RouteName() {
  return (
    <Curious>
      {({ response }) => <BaseRouteName response={response} />}
    </Curious>
  );
}

Please check out the full @curi/react-dom API documentation to see every component that the package provides.

React Native Tips

Note: This guide assumes that you are already familiar with React Native.

Back Button

To add back button support, you need to use your history object (which you can use directly or access through your router).

The history.go() method is used for jumping between locations, so passing it -1 will jump back to the previous location.

When the app is at the initial location, you may want to return false to close the app when the user presses the back button.

import { BackHandler } from 'react-native';

// create your router
const router = curi(history, routes);

BackHandler.addEventListener(
  "hardwareBackPress",
  () => {
    // close the app when pressing back button
    // while on the initial screen
    if (router.history.index === 0) {
      return false;
    }
    router.history.go(-1);
    return true;
  }
);