React Router v2/3
Curi is mostly conceptually similar to React Router versions 2 and 3.
- Both use a centralized router.
- Both routers are made up of route objects (although with React Router some of these are disguised as JSX with
- With both Reaft Router and Curi, routes can be nested. This can be used to specify child routes that build off of the paths from their parent routes.
Migration from React Router v2/3 to Curi should not require a complete reworking of your application, but there are some key differences.
- Curi's routing is handled entirely outside of React; there are no
- With Curi, when a nested route matches, only that route renders. Any ancestor routes that also (partially) match are not rendered. This is different from React Router, where ancestors of the best matched route also render.
Let’s get started with setting up our routes.
Both styles described above define the same route structure for three routes:
/inbox/:message. Each one of these has a component that will be rendered when it matches. The
/inbox/:message route has some methods defined to describe its behavior when the route enters, updates, and leaves.
path-to-regexp. You can read learn how to format paths from the
First, we will define the names and paths for our routes.
Each route must also have a unique name. A route's name will be used for interacting with it. For example, to navigate to a route, you only have to know its name, not its URL.
The biggest difference between the Curi paths and the React Router paths is that with Curi, you never include a forward slash at the beginning of the path. This means that while the root path for React Router is
'/', the root path for Curi is
Next, we should add our components to each route. We will ignore the
App component that is used in the React Router routes. That is not route specific and will be rendered by our application (assuming we actually need it).
With Curi, the router creates a "response" object when it matches locations. Some of the properties of the response are automatically set based on the location and the matching route. Others can be set by a route. This is done using the
respond property, which is a function that returns an object whose properties will be added to the response. For this React application, we want a response's
body property to be the React component associated with each route.
We are close to replicating our React Router routes, but we still need to implement the
on___ methods for our
:message route. With Curi, routes can have a
resolve function that is called when the route matches the new location.
With React Router,
onEnter is called when the route first matches, while
onChange is called when the same route matches a new location (e.g. with new path parameters).
onChange are nearly the same; the big difference between the two is that
onChange will receive the previous props, which could be used to determine which props changed. The functionality for both
onChange can be covered using a
There is no equivalent to
onLeave with Curi. If you have something you need this functionality for, please open up an issue in the GitHub repo.
@curi/router route API documentation covers all of the route properties.
Once your routes have been defined, you can move on to creating your Curi router.
With React Router, you create your router by rendering a
Router. That either takes the
Route components as props or the route objects through its
routes prop. The
Router also takes a
history prop, which is either one of the pre-routerured objects (
hashHistory) or one that you create yourself.
With Curi, the router is created prior to rendering. It takes a Hickory history function, your routes array, and possibly an options object. Hickory is similar to the
history package used by React Router, but has an API tailored for asynchronous applications.
We will walk through the rendering differences between React Router and Curi by looking at what happens in each when we navigate to the URI
React Router uses the
Router component to subscribe to location changes. Each time that the location changes, it walks over its routes and determines which route(s!) match.
React Router starts by rendering the root component. In the above router, that is the
App. Next, our
inbox route also matches, so React Router also renders our
Inbox component. Finally, the URI
/inbox/test also matches our
:message route (which is concatenated with its parents to form the path
Message is rendered as well. Each child component is rendered by its parent, so we end up with a component tree that looks something like this:
With this structure, any routes with children will be rendered when one of the children matches. That means that those routes need to know how to render based on what type of match they have. For example,
Inbox needs to know how to render for an exact match (the URI is
/inbox) and for a partial match (
/inbox/test). Also, if the
Inbox needs to pass any props to
Message, it has to use
React.cloneElement, which works but is not the cleanest looking code.
With Curi, we also need to re-render our application every time that the location changes. We will do this by creating a root Curi component by calling the
createRouterComponent function, which comes from the
@curi/react-dom package, and passing it our Curi router. While the name of this component is entirely up to you, we will refer to it as the
Router will setup an observer on the provided router so that it can re-render your application whenever there is a new
Router uses a context provider that makes a response available to other components in the application using the
useResponse hook returns an object with two properties:
responseis the new response object
navigationis an object with additional information about the navigation
The router can also be accessed throughout the application using the
Above, we added
respond functions to each route. The functions set React components as the
body property of responses. We can now use
response.body to render those components.
In the React Router section, we had three components that were rendered:
Message. With Curi, only the most accurately matched route actually matches. That means that for the URL
"Message" route will match, but its parent route,
"Inbox" will not, so
response.body will be the
Message component. Unlike React Router, we don’t render
Inbox because we did not match the
It was mentioned above that there is no need for the
App component with Curi. If you want to have an
App component, you can render it either inside of the
children function or as a parent of your
Router. This can be useful for rendering content that is unrelated to specific routes, like a page header or menu.
App inside of the
children function is necessary if any of the components rendered by the
App are location aware components, since they need to access the Curi router (through React’s context, which the
What about props that you want to send to your route components? Pass them to the
Body component that you render. Props can be passed individually, but passing the whole
response object is recommended.
React Router provides a
withRouter higher-order component that will inject router props into the wrapped component.
The best way to get router data with Curi is to use the
At this point, hopefully you are comfortable with migrating from React Router v2/3 to Curi. If there are any concepts not covered here that you think should be, please feel free to open up an issue on GitHub.