As a developer, you’re always looking for ways to improve the performance and functionality of your projects. The PWA Kit includes several features and functions that make creating high-performance, mobile- and SEO-friendly web applications accessible.
In this article, we’ll explore one of the critical features of the PWA Kit: the getProps method.
Before we get started
This article assumes you have experience with React and the libraries the PWA Kit uses behind the scenes. Is this not the case? Not to worry, there is plenty of public information available to get you started:
React Router
For navigation, the PWA Kit uses React Router v5, a popular routing library for React.
React Router offers a variety of options for constructing path strings, including the ability to specify more than one path for the same component and use regular expressions to match paths that follow a pattern.
Express
The PWA Kit uses Express, a popular web application framework for Node.js, to handle server-side rendering and API requests. Express is known for its simplicity, flexibility, and robust features, making it an excellent choice for building scalable, high-performance web applications.
In the PWA Kit, Express handles the server-side rendering of React components, improving our SPA’s (Single Page Application) performance and SEO-friendliness.
What is the getProps method?
The getProps method is used to supply data fetched from API requests to the routeComponent via the props object. When a component from the routes array is rendered, the component’s getProps method is supplied with a single JavaScript object that contains information about the rendering context, such as the URL of the request and any named route parameters.
routeComponent
“routeComponent is a higher-order PWA Kit React SDK component that enhances each component specified in the routes array. When a component is enhanced by “routeComponent“, it gains access to several static methods, including two important ones that storefront developers can customise: getProps and shouldGetProps.
Here is a snippet from the PWA Kit where the “wrapping” happens on the client side.
const props = {
error: window.__ERROR__,
locals: locals,
routes: getRoutes(locals),
WrappedApp: routeComponent(App, false, locals)
}
// ...
export const OuterApp = ({routes, error, WrappedApp, locals}) => {
const AppConfig = getAppConfig()
const isInitialPageRef = useRef(true)
return (
)
}
getProps Example
const ProductDetails = ({name}) => (
{name}
)
ProductDetails.getProps = async () => {
const response = await fetch(`https://httpbin.org/status/404`)
const data = await response.json()
return data // {name: "product_name}
}
In this example, we define a ProductDetails component that takes one prop: name. We also define a getProps function for the component that fetches data from an API and returns it as props.
getProps Object Parameter
The getProps method receives a JavaScript object with properties based on the rendering context. These include:
- params: contains object properties corresponding to named route parameters
- req: an enhanced version of Node’s request object for HTTP requests
- res: representing the HTTP response.
- location: the URL of the request and is available on both client and server sides, but is not part of the Express API.
ProductDetails.getProps = async ({params, req, res, location}) => {
...
}
Add your own properties
To add custom properties to the object passed to the getProps function, define a function called extraGetPropsArgs as a property of the AppConfig component. This function can extend the set of arguments that get passed to all components enhanced by routeComponent via the getProps function.
For example, the Retail React App uses the extraGetPropsArgs function to give all components enhanced by routeComponent access to an object for interacting with the Salesforce Commerce API.
This is achieved by defining the extraGetPropsArgs function to return an object with the API property set to locals.api.
Here is an example of how it is defined in AppConfig:
AppConfig.restore = (locals = {}) => {
locals.api = new CommerceAPI(apiConfig)
}
AppConfig.freeze = () => undefined
AppConfig.extraGetPropsArgs = (locals = {}) => {
return {
api: locals.api
}
}
And now we can use that new property in our ProductDetail component:
ProductDetail.getProps = async ({res, params, location, api}) => {
const {productId} = params
let category, product
const urlParams = new URLSearchParams(location.search)
product = await api.shopperProducts.getProduct({
parameters: {
id: urlParams.get('pid') || productId,
allImages: true
}
})
...
}
Handling errors
To handle errors in a getProps function, you have two options.
The first option is to throw an HTTPError object, which can be imported from “pwa-kit-react-sdk/ssr/universal/errors”. When you throw an HTTPError, a dedicated Error component is rendered.
The second option is to use props to inform the rendered component of the error so that it can be used in custom error-handling logic.
Here’s an example of how to handle errors in a getProps function:
ProductDetails.getProps = async ({res}) => {
const response = await fetch(`https://httpbin.org/status/404`)
if (!response.ok) {
if (response.status !== 404) {
throw new HTTPError(response.status, response.statusText)
}
res && res.status(404)
return {errorProductNotFound: true}
}
const data = await response.json()
return data
}
Things to keep in mind
When writing getProps functions, it’s important to be selective in what data to return to keep the size of the rendered HTML down. Besides HTML size, any external API call you do will also impact the response time of that page!
You should also write conditional code in your components to handle undefined props, and render a placeholder component while props are undefined.
const ProductDetails = ({name}) => (
{name ?? 'My fallback'}
)
ProductDetails.getProps = async ({params}) => {
const response = await fetch(`https://api.example.com/products/${params.productId}`)
const data = await response.json()
return data || {}
}
Caching
Just like in SiteGenesis or SFRA it is possible to cache the server-side response! By setting the ‘Cache-Control‘ header, the CDN knows that it should cache the response.
const ProductDetails = ({name}) => (
{name ?? 'My fallback'}
)
ProductDetails.getProps = async ({params, res}) => {
const response = await fetch(`https://api.example.com/products/${params.productId}`)
const data = await response.json()
// If res exists, we are on the server-side
if (res) {
res.set('Cache-Control', `max-age=${MAX_CACHE_AGE}`)
}
return data || {}
}
Personalisation
Another thing to remember is that the server side uses a “guest” token, meaning no personalisation can happen there. It makes sense, but watch out with caching (see the previous section)!
The PWA Kit has no concept of “varyby=price_promotion” caching. That is the responsibility of the SCAPI!
Client Side Execution
Once the first page loads, the responsibility of displaying the content shifts from the server to your device through a process known as hydration. This is when your React app starts working in your browser.
As you interact with the page, it responds to your actions and shows the necessary elements. This efficient and user-focused approach creates the smooth experience React is known for.
The PWA Kit architecture ensures a seamless transition from server-side to client-side rendering. For instance, all components are prepared with the required information, even if it comes from API requests. This data is embedded into the page source during server-side rendering, making it available during hydration.
After this transition, you lose access to some “server-side” properties in the getProps function, such as res (Express).
Conclusion
For one method, there is a lot to write about and to keep in mind! The PWA Kit has many features available, ready for use – you just need to know they exist!