The guide walks you through handling multiple screen sizes when laying out your UI with React Storefront.
React Storefront builds upon Material UI's responsive capabilities. Material UI provides 5 breakpoints by default:
These are set in the breakpoints.values in the theme.
For more information see Material UI - Breakpoints
The best way to implement responsive layout in your components is to use the theme to generate breakpoint selectors in your component styles. This is both cache-friendly and SSR compatible. Here's an example where we swap the orientation of the main image carousel and product information for a PDP from vertical to horizontal as the screen size increases past the sm
breakpoint:
import React, { Component } from 'react';
import { withStyles } from '@material-ui/core/styles'
import Typography from '@material-ui/core/Typography'
import Container from 'react-storefront/Container'
import ImageSwitcher from 'react-storefront/ImageSwitcher'
import { inject, observer } from 'mobx-react'
@withStyles(theme => ({
header: {
[theme.breakpoints.down('sm')]: {
flexDirection: 'column',
alignItems: 'stretch'
},
[theme.breakpoints.up('md')]: {
flexDirection: 'row'
}
}
info: {
[theme.breakpoints.up('md')]: {
flex: 3
}
},
images: {
[theme.breakpoints.down('sm')]: {
height: 'calc(100vh - 300px)'
},
[theme.breakpoints.up('md')]: {
flex: 1
}
}
}))
@inject(({ app }) => ({ product: app.product }))
@observer
export default class Product extends Component {
render() {
const { classes, product } = this.props
return (
<Container>
<div className={classes.header}>
<ImageSwitcher className={classes.images} product={product}/>
<div className={info}>
<Typography variant="h6">{product.name}</Typography>
<Typography>{product.description}</Typography>
</div>
</div>
</Container>
)
}
}
Hidden
Material UI's Hidden component provides a handy way to hide specific elements based on viewport width. Always assign implementation="css"
so that the result is compatible with server side rendering.
@withWidth
When CSS isn't enough, you can inject the current viewport width into your component as a prop using Material UI's withWidth function.
Using @withWidth
means that the value of initialWidth
in AppModelBase
will be used during server-side rendering.
When rendering AMP content, app.initialWidth
is set to "xs". Since Google only delivers AMP pages on mobile devices, React Storefront assumes a small screen size when rendering amphtml.
When using @withWidth
you need to consider how this affects server-side caching. By default, React Storefront caches a single response for each URL, so all devices will get the same initial server side rendering, regardless of screen size.
You can cache multiple responses for different devices based on the user agent by specifying a key
function in your routes cache
handler:
router.get('/p/:id',
fromClient({ page: 'Product' }),
fromServer('./product/product-handler'),
cache({ maxAgeSeconds: 300, key: (request, defaults) => {
const ua = new UAParser(env.user_agent)
const os = ua.getOS()
return {
...defaults,
initialWidth: os.name.match(/iOS|Android/) ? 'xs' : 'xl'
}
}})
)
In the example above, we add initialWidth
to the default key generated by React Storefront to create a custom key that allows us to cache separate responses for desktop and mobile user agents.
React Storefront provides a number of components with built-in responsive behavior.
Container
React Storefront's Container
component defines a maximum width for body content and sures that the content remains centered when the viewport exceeds the maximum width. It is recommended that you use a Container as the root component of all of your pages.
The maximum width is determined by theme.maxWidth
.
AppBar
AppBar
can be configured to automatically hide the hamburger button for desktop devices by setting the responsive
prop to true
. It also constrains its inner contents to theme.maxWidth
.
HeaderLogo
HeaderLogo
will automatically center align for mobile devices and left align for desktop devices.
NavTabs
NavTabs
constrains its inner contents to theme.maxWidth
and provides scrollers on overflow.
ResponsiveTiles
Product listings are often implemented as a set of tiles. The ResponsiveTiles
component provides a foundation for this type of UI. It automatically sets the number of columns of tiles based on theme breakpoints.
<ResponsiveTiles>
{ subcategory.items.map((product, i) => (
<Link key={i} to={`/p/${product.id}`} className={classes.link}>
<Vbox alignItems="stretch">
<div className={classes.thumb}>
<Image eager={index < 4 || index >= 10} aspectRatio={100} alt="product" src={product.thumbnail}/>
</div>
<div>
<Typography variant="subtitle1" className={classes.name}>
{product.name}
</Typography>
<Rating product={product} className={classes.rating}/>
<Typography className={classes.price}>{ price(product.price) }</Typography>
</div>
</Vbox>
</Link>
))}
</ResponsiveTiles>