React Storefront
|

Late Loading Personalized Content

In order to keep your app fast, we recommend making all pages in the core shopping flow cacheable. This is done by adding a cache() handler to your routes. For example:

new Router().get(
  '/p/:id',
  cache({
    client: true,
    edge: {
      maxAgeSeconds: 60 * 60 * 24 // 24 hours
    }
  }),
  fromClient({ page: 'Product' }),
  fromServer('./product/product-handler')
)

It is common, however, to display personalized, non-cacheable content throughout the shopping flow. React Storefront provides a standard pattern for late-loading personalized content once the app loads in the browser.

@withPersonalization(branch)

To late-load personalized data when a page is displayed, add the @withPersonalization decorator to your page component. An example of this can be seen in the src/product/Product.js component in your starter app:

// src/product/Product.js

import React, { Component } from 'react'
import withPersonalization from 'react-storefront/personal/withPersonalization'
import { observer, inject } from 'mobx-react'

@withStyles(styles)
@inject('app')
@withPersonalization(app => app.product) // automatically calls ProductModel.loadPersonalization() when the user views a product
@observer
export default class Product extends Component {
  // ...
}

The @withPersonalization decorator takes a function that returns a model that implements the loadPersonalization() action. This action will be called when the user navigates to this page component.

loadPersonalization()

The loadPersonalization model action is called by @withPersonalization. You can add this action to any model to late load non-cacheable, personalized content. Here's an example from the ProductModel in your starter app.

// src/product/ProductModel.js

import { types, flow } from 'mobx-state-tree'
import ProductModelBase from 'react-storefront/model/ProductModelBase'

const ProductModel = types.compose(
  ProductModelBase,
  types
    .model('ProductModel', {
      // additional product fields go here
      specs: types.maybeNull(types.string),
      reviews: types.optional(types.array(types.string), []),
      recommendations: types.maybeNull(types.array(types.late(() => ProductModel)))
    })
    .actions(self => ({
      /**
       * Here's an example of how you can late load personalized data.  Doing
       * so allows your main product route to be cached on the server, which is
       * essential for achieving the best possible performance.  Any data that is not cacheable,
       * such as personalized product recommendations should be late loaded here.
       */
      loadPersonalization: flow(function*() {
        if (self.recommendations == null) {
          const { recommendations } = yield fetch(`/p/${self.id}/personalization.json`).then(res =>
            res.json()
          )
          self.recommendations = recommendations
        }
      })
    }))
)

export default ProductModel

usePersonalization(branch)

Personalization is also implemented as a hook:

import { useObserver } from 'mobx-react-lite'
import AppContext from 'react-storefront/AppContext'

function Product() {
  return useObserver(() => {
    const {
      app: { product }
    } = useContext(AppContext)

    usePersonalization(app => app.product) // automatically calls ProductModel.loadPersonalization() when the user views a product

    // ...
  })
}