New Responses

Curi uses an observer pattern to call registered functions (called response handlers) when there is a new response. The primary use care for this is to re-render the application whenever there is a new response, but other functionalities (like logging) can also be performed.

Response Handlers

When response handlers are called, they are passed an object with three properties: router, response, and navigation. Which objects/properties you use depends on what the response handler is doing.

function responseHandler({
  router,
  response,
  navigation
}) {
  // ...
}

Registering Response Handlers

There are three ways to attach response handlers to the router: router.once and router.observe or as a side effect.

Response handlers registered with router.once will only be called one time, while those registered with router.observe and side effects will be called for every new response.

When you register a response handler using router.observe, it will return a function that you can use to stop calling the response handler for new responses. You should rarely need to do this, but it can be useful for memory management if you are adding and removing lots of observers.

// fn will only be called one time
router.once(fn);

// obs will be called for every new response
let stop = router.observe(fn);

Use Cases

What should you use response handlers for?

Setup

If any of the routes in an application have resolve functions, when they match their responses are created asynchronously. When the application first renders, if the router matches an async route, the response isn't immediately ready to use. To deal with this, you can use an observer to render once the initial response is ready.

A setup function only needs to be called one time, so you can register it with router.once.

let Router = createRouterComponent(router);

function setup() {
  ReactDOM.render((
    <Router>
      <App />
    </Router>
  ), document.getElementById('root'));
}

router.once(setup);

Rendering

Rendering libraries need to know when there is a new response so that they can re-render the application.

The Curi rendering packages (@curi/react-dom, @curi/react-native, @curi/vue, and @curi/svelte) setup an observer internally so that they can automatically re-render.

If you are using vanilla JavaScript to render your application or you are writing your own framework implementation, you would use router.observe to re-render new responses.

function observer({ response }) {
  // let the app know there is a new response
}

router.observe(observer);

Side Effects

Side effects are observers that are provided to the router at creation instead of by calling router.observe. These can be useful for tasks that are not rendering related as well as for tasks that need to be performed after a render has completed.

The title function exported by @curi/router is a side effect that will use response.meta.title to set the page's document.title.

With single-page applications, clicking on links wish hashes won't always scroll to the matching element in the page. The scroll function exported by @curi/router is a side effect that scrolls the page to the element that matches the new response's hash (response.location.hash) after the new response has rendered.

If you need to add logging to your application, you could write your own observer to do this. Your observer can either be added as a side effect when the router is constructed or later using router.observe.

function logger({ response }) {
  loggingAPI.add(response.location);
}

// as a side-effect
let router = createRouter(browser, routes, {
  sideEffects: [{ fn: logger }]
});

// as an observer
router.observe(logger);