JavaScript mobile rendering pattern for react
Do you find yourself writing lot of condition checks what the viewport is, and then render the viewport specific user interface for the component in your applications. If you feel the pain of maintaining those lot of conditions in your code, you have come at right place.
Read below to find out how we can move all this ugly condition checks spread all across your rendering code at one place. With this pattern your code becomes more readable and maintainable, still maintaining all the flexibility of having different implementations as per viewport.
This article assumes that you understand the basics of react. How to write a react component.
Decorators
As per wikipedia decorator pattern is a design pattern that allows behaviour to be added to an individual object. Well in our case the object appears to be a ES6 class which will be decorated with added responsibility of understanding how to render mobile implementation.
Let’s dive into how the decorators can help us in this.
Using decorator @mobileRenderable
Assume you have a component called MyComponent. MyComponent has two different unique implementations which are rendered on basis of viewport you are in.
Your regular MyComponent will look like following
class MyComponent extends React.Component {
render(){
return(<div>Awesome My Component</div>)
}
}
Now if you need this same component with similar data but to be differently rendered in mobile. You can use mobileRendrable decorator like following for that.
@mobileRenderable
class MyComponent extends React.Component {
renderMobile(){
return(<div>Aswesome My Component rendered for mobile </div>)
}
render(){
return(<div>Awesome My Component</div>)
}
}
Noticed @mobileRenderable at the top of the class definition. mobileRenderable tells the class that this class has a different implementation for mobile. While rendering this component while on a mobile viewport, renderMobile should be used instead of render. Isn’t that clean? But wait, how do you decide whats mobile and whats not. Well to understand that we have to dive into the implementation of the @mobileRenderable decorator.
Implementing @mobileRenderable
If you are new to decorators in JavaScript, I suggest reading Exploring ES7 decorators by Addy Osmani. You can continue with this article and may go back to decorator basics if it starts getting confusing.
A simple bare minimum implementation depicting the concept will be as follows
export const mobileRenderable = function (target, name, descriptor) {
const originalRender = target.prototype.render
let mobileRender = target.prototype.renderMobile
//Device identification
let device = deviceIdentification().formFactor || 'desktop'
let functionToCall = null
if (device === 'mobile') {
functionToCall = mobileRender
} else {
functionToCall = originalRender
}
//Overwrite render with render method to call on basis of form factor
target.prototype.render = functionToCall
}
In one line the implementation can be explained as. Replace the render method of React component getting rendered with renderMobile method if we are in mobile form factor.
Implementation of deviceIdentification() is left to the implementor of the decorator. deviceIdentification function identifies the device and provides that data to decorator to take the decision needed.
Doing this requires some housekeeping around function definitions. A complete implementation will look like following.
export const mobileRenderable = function (target, name, descriptor) {
//Do this only on client side
if (canUseDOM) {
console.log('mobileRenderable getting applied client')
const originalRender = target.prototype.render
let mobileRender = target.prototype.renderMobile
//Throw warning if user has used mobileRenderable but have not provided renderMobile
if (!mobileRender) {
console.warn(target, ' is decorated with mobileRenderable but renderMobile function is not provided. Regular render will be used.')
mobileRender = originalRender
}
//Device identification
let device = deviceIdentification().formFactor || 'desktop'
let functionToCall = null
if (device === 'mobile') {
functionToCall = mobileRender
} else {
functionToCall = originalRender
}
target.prototype.render = functionToCall
//Keep reference of the original render method in case you need it in renderMobile
target.prototype.__render = target.prototype.__render || originalRender
}
}
Above implementation does two more things including basic implementation
- Throws warning if mobileRender function is not provided.
- Original render method is kept for reference under property name __render in case it is needed inside renderMobile method.
Conclusion
With this pattern, code in your application will be more maintainable since the decision is taken quite earlier in life cycle of the application at the class compile time. You can create multiple versions of the mobileRenderable, like tabletRenderable as per your needs. Decision of what is mobile is left to the users choice. It can be viewport or it can be user agent sniffing.
I hope you found this article useful. If you have any comments / suggestions please feel free to comment.