@curi/react

About#

The @curi/react package provides a number of React components that you can use for rendering your application.

Installation#

If you have Node and NPM installed, you can install the package through npm (or yarn if you prefer).

npm install @curi/react
yarn add @curi/react

Prefer inline scripts? Every version is available through Unpkg.

There are both full and minified versions available.

You can access the package via window.CuriReact.

<script src="https://unpkg.com/@curi/react@1.0.0-beta.29/dist/curi-react.js"></script>

API#

<CuriProvider>#

The <CuriProvider> is the root Curi component for an application. It has two jobs:

  1. Re-rendering the application when a new response is emitted.
  2. Placing values on the context so that the other Curi components can access them.
Note: All of the other components provided by @curi/react must be descendants of a <CuriProvider>.

<CuriProvider> will observe your router so that it can automatically re-render your application after navigation.

import { CuriProvider } from '@curi/react';
            
const App = () => (
  <CuriProvider router={router}>
    {({ response, navigation, router }) => {
      const { body:Body } = response;
      return <Body response={response} />;
    }}
  </CuriProvider>
);

Props#

router#

A Curi router.

children#

children is a render-invoked function. When it is called, it will be passed an object with three properties:

propertydescription
responsethe response object generated for the current location
navigationthe action of the navigation and the previous response object
routerthe Curi router

<Focus>#

<Focus> lets you focus a DOM element whenever there is a new response. Its children prop is a render-invoked function that receives a React ref, which should be attached to the DOM component that you want to be focused.

The DOM component that gets the ref should either already be "focusable", like an <input>, or be given a tabIndex prop (usually with the value of -1). If neither of these conditions is met, then the document's <body> will be focused.

The focused element will have an outline (the exact style varies by browser). You can remove this visual with a CSS outline of "none".

Note: You should only have one <Focus> in an app.
import { Focus } from "@curi/react";

<Focus>
  {ref => (
    <div tabIndex={-1} ref={ref}>
      {/* ... */}
    </div>
  )}
</Focus>

<Curious>#

A context consumer component for injecting router values into components.

import { Curious } from '@curi/react';

const MyComponent = () => (
  <Curious>
    {({ router, response, navigation }) => {
      // pass these props to any components
      // that needs them
      return (
        <ThingThatNeedsResponse
          response={response}
        />
      );
    }}
  </Curious>
);

Props#

children#

A render-invoked function that returns a React element. This function will receive an object with router, response and navigation properties.

<Active>#

The <Active> component is used to render based on whether or not a route is "active" (its name and params match the current response's name and params) using a render-invoked children function.

import { Active } from '@curi/react';

const ActiveLink = ({ to, params, partial, ...rest}) => (
  <Active name={to} params={params} partial={partial}>
    {active => (
      <Link
        to={to}
        params={params}
        {...rest}
        className={active ? "active" : ""}
      />
    )}
  </Active>
);

<ActiveLink to="Home">Home</ActiveLink>
Note:

This relies on the active route interaction from @curi/route-active being added to your router.

import active from '@curi/route-active';

const router = curi(history, routes, {
  route: [active()]
});

Props#

name#

The name of the route to compare against the response object.

params#

An object containing route parameters. These will be compared against the route params of the response object.

children#

A render-invoked function whose first argument is whether the route (determined using the name and params) is active.

// response = { name: "Photo", params: { id: "abcde" }}

<Active name="Photo" params={{ id: "abcde" }}>
  {active => ( // if active === true
    <Photo className={active ? "active" : "inactive"} />
  )}
</Active>
// <Photo className="active" />

<Active name="Photo" params={{ id: "qwerty" }}>
  {active => ( // if active === false
    <Photo className={active ? "active" : "inactive"} />
  )}
</Active>
// <Photo className="inactive" />

The second argument passed to the render-invoked function is the current response. <Active> only checks if the route is active (i.e. matches the current location's pathname). If you want to check if a query or hash match the current location, you should do this yourself inside of the render-invoked function. You can compare the query/hash against the response's location.

<Active name="Home">
  {(active, response) => {
    const activeHash = response.hash === "ahoy"
    // ...
  }}
</Active>

partial#

When true, partial allows ancestor routes to be considered active. Defaults to false.

// response = { name: "Photo", params: { id: "abcde" }}
// where "Photo" is a child route of "Album"

<Active name="Album">
  {active => ( // if active === false
    <Album className={active ? "active" : "inactive"} />
  )}
</Active>
// <Album className="inactive" />

<Active name="Album" partial={true}>
  {active => ( // if active === true
    <Album className={active ? "active" : "inactive"} />
  )}
</Active>
// <Album className="active" />

<Prefetch>#

The <Prefetch> component is used for calling a route's on methods when a specified element becomes visible in the page. The specified element's visibility is monitored by the IntersectionObserver API.

<Prefetch> uses a render-invoked children function to provide a ref for the element that should be observed.

import { Prefetch } from '@curi/react';

<Prefetch
  match={{ name: "User", params: { id: 1 } }}
>
  {ref => <div ref={ref}>User 1</div>}
</Prefetch>
// when the <div> becomes visible, the "User" route's
// on.initial() and on.every() functions will be called
Note:

This component relies on @curi/route-prefetch, so make sure to include that when you create your router.

import prefetch from "@curi/route-prefetch";
              
const router = curi(history, routes, {
  router: [ prefetch() ]
});

Props#

match#

The match object that will be passed to on functions. The only required property of this object is name because <Prefetch> needs to know which route's on functions should be called.

<Prefetch match={{ name: "Home" }}>
  {...}
</Prefetch>

children()#

The children prop is a render-invoked function that receives two arguments:

  1. The ref to attach to the component that should be observed.

    If you forget to attach the ref to a component, an error message will be logged in the console.

  2. The resolved object returned by the prefetch() route interaction.

    This will be null until the data for the route has been prefetched. Once the prefetch has completed, resolved will be an object with initial, every, and error properties (error is set if either on.initial() or on.every() throw and you do not catch it). The resolved values can be used by your app or you can just use the knowledge that the data has finished loading to render an indicator.

<Prefetch match={{ name: "Home" }}>
  {(ref, resolved) => (
    <div ref={ref}>
      <h1>Home</h1>
      {resolved ? "Loaded" : "Loading"}
    </div>
  )}
</Prefetch>

which#

which is an array whose values are the names of a route's async match functions to call.

If which is not provided, all of a route's match functions will be called.

// only call match.one()
<Prefetch match={...} which={["one"]}>
  {...}
</Prefetch>

// call all match functions
<Prefetch match={...}>
  {...}
</Prefetch>

ref#

The <Prefetch> usually creates a ref for you, but you can provide a ref yourself if you need access to the DOM element elsewhere.

Note: <Prefetch> only works with refs created using React.createRef().
const ref = React.createRef();

<Prefetch match={...} ref={ref}>
 {...}
</Prefetch>

<Block>#

The <Block> component lets you prevent navigation until a user has confirmed that they want to navigate. This can be useful when the user attempts to navigate away from a partially filled form. This will not prevent the user from navigating to another site, it only works for navigation within the application.

import { Block } from '@curi/react';

Props#

active#

A boolean, which is true by default. When it is true, the navigation block is active. When it is false, navigation will not be blocked.

// will block navigation
<Block active={true} confirm={confirm} />

// will not block navigation
<Block active={false} confirm={confirm} />

confirm#

The confirm prop is a function that will be called whenever there is navigation.

argumentdescription
locationthe location that is being navigated to
actionthe type of navigation
successa function to call when navigation should happen
failurea function to call when navigation should be cancelled.
<Block
  confirm={({ location, action }, success, failure) => {
    const response = window.confirm("Shall we?");
    if (response) {
      success();
    } else {
      failure();
    }
  }}
/>