React integration
React integration
This is for:
DeveloperQubit React provides a method of controlling the rendering of React components from within a Qubit Experience.
Because the rendering can be controlled from within the experience, you can, as with all Qubit Experiences, create one or more variations, possibly even with a different rendering in each variation, target it with segments and track the impact of rendering changes on key metrics defined in your experience goals.
Website implementation
In this we will look at the changes that you need to make to your website’s codebase.
To expose a component for use in Experiences, wrap the relevant components with qubit-react/wrapper
.
import QubitReact from 'qubit-react/wrapper'
<QubitReact id='header' ...props>
<Header ...props />
</QubitReact>
A unique id
is required for each wrapped component.
We recommended that all properties passed to the wrapped component are also passed to the wrapper.
All the properties passed to the wrapper component will be forwarded to your custom render function.
Note
You can find the source code for the |
Usage
In this section we will look at the code that you write inside Qubit’s platform.
Activation
Open your experience and select Settings. Select Edit in the Triggers card and then next to Custom JavaScript (default).
This opens an editor that you can use to edit the default code block:
module.exports = function triggers (options, cb) {
cb(true)
}
Qubit React uses a wrapper ownership concept to ensure that only one experience can control the contents of a wrapper at any given time. This reduces conflicts between experiences attempting to modify the same component.
Ownership will not be taken until the options.react.render()
method is called in the experience execution function.
It is your job to ensure that multiple experiences do not try to execute at the same time.
If they do, only the first experience will execute succesfully.
All subsequent attempts will result in an error being thrown.
Before taking ownership of a wrapper, you first need to register it during the experience activation phase, using the options.react.register()
method.
You can register multiple wrappers by passing in multiple wrapper Ids.
This will return a promise that resolves once React is available and all registered wrappers are ready to render on the page.
Example:
module.exports = function triggers (options, cb) {
options.react.register(['header']).then(cb)
}
Remember, it is your responsibility to make sure two experiences don’t try and claim the same wrapper at the same time. If they do, one of them will end up throwing an exception during execution. |
Execution
Render content
Now that we are finally in execution phase, let’s render some custom content into our wrapped component.
Example:
module.exports = function experienceExecution (options) {
const React = options.react.getReact()
class NewHeader extends React.Component {
render () {
return <h2>NEW HEADER</h2>
}
}
options.react.render('header', function (props) {
return <NewHeader />
})
}
Rendering original content
Sometimes, it might be useful to render the original content temporarily. For example, you may want to show the original content and render something custom again at a later point.
Example:
module.exports = function experienceExecution (options) {
const React = options.react.getReact()
setTimeout(() => {
// renders original content
options.react.render('header', function (props) {
return props.children
})
setTimeout(() => {
// renders new content again
options.react.render('header', function (props) {
return <NewContent />
})
}, 5000)
}, 5000)
}
Manually releasing ownership
Qubit Experiences will automatically take care of releasing wrapper ownership when experiences restart due to virtual page views. If for some reason you need to release ownership under other circumstances, there is a method available to do so:
module.exports = function experienceExecution (options) {
options.react.render('header', () => <NewContent />)
setTimeout(() => {
options.react.release()
}, 5000)
}
Leading-practice
Calling |
Real world example
Activation
Example:
module.exports = function experienceActivation (options, cb) {
const saleEnds = Date.UTC(2017, 0, 1, 0, 0, 0)
const remaining = saleEnds - Date.now()
if (remaining > (60 * 60 * 1000)) return
if (remaining < 0) return
options.state.set('saleEnds', saleEnds)
options.react.register(['header-subtitle-text', 'promo-banner-text'], cb)
}
Execution
Example:
module.exports = function experienceExecution (options) {
const saleEnds = options.state.get('saleEnds')
const React = options.react.getReact()
class Countdown extends React.Component {
componentWillMount () {
const { endDate } = this.props
this.state = {
remaining: endDate - Date.now()
}
const interval = setInterval(() => {
const remaining = endDate - Date.now()
this.setState({ remaining: endDate - Date.now() })
if (remaining < 0) {
clearInterval(interval)
options.react.release()
}
}, 1000)
}
render () {
let secsLeft = Math.floor(this.state.remaining / 1000)
const minsLeft = Math.floor(secsLeft / 60)
secsLeft = secsLeft - (minsLeft * 60)
return <span>{`${minsLeft} mins and ${secsLeft} secs left`}</span>
}
}
options.react.render('header-subtitle-text', function (props) {
return <span>Great offers somewhere...</span>
})
options.react.render('promo-banner-text', function (props) {
return <Countdown endDate={saleEnds} />
})
}
Debugging
Qubit React uses driftwood for logging. Enable it via the Developer Console.
window.__qubit.logger.enable({ 'qubit-react:*': null }, { persist: true })
// enable qubit-react logs
window.__qubit.logger.enable({ '*': null }, { persist: true })
// enable all logs
window.__qubit.logger.disable()
// disable logs
See driftwood to see the full API documentation.
FAQs
How and why should I migrate from the legacy qubit-react/experience
package in my Experience code?
At the beginning of January 2020, we deprecated the old callback-based wrapper registration method and introduced a new promised-based one in its place. This was done for two main reasons:
-
The old API was overly-verbose and complex
-
Under certain scenarios, wrapper ownership would be claimed when an experience wasn’t actually going to fire, preventing all other experiences from claiming that wrapper
While both APIs are currently available, we will be removing support for the old callback-based registration in version 2.0 of the qubit-react/experience
package.
Here is an example of how to upgrade to the new format:
Old:
module.exports = function triggers (options) {
const experience = require('qubit-react/experience')(options.meta)
const release = experience.register(['header'], function (slots, React) {
options.state.set('slots', slots)
options.state.set('React', React)
})
options.onRemove(release)
return true
}
module.exports = function variation (options) {
const React = options.state.get('React')
const slots = options.state.get('slots')
options.react.render('header', () => <div>New header!</div>)
options.onRemove(slots.release)
}
New:
module.exports = function triggers (options) {
// In the new version, the slots and React instance are accessed via the dedicated
// API interface, so there is no need to manually pass them through state.
return options.react.register(['header'])
// There is also no need to return a remove handler, because it is done for you.
}
module.exports = function variation (options) {
const React = options.react.getReact()
options.react.render('header', () => <div>New header!</div>)
}
Is it possible to disable Qubit Experiences in a testing environment?
Yes.
If you’re not loading Qubit’s smartserve.js
script in your testing environment then this shouldn’t be an issue.
Qubit React wrappers are a transparent noop pass through in that case, and should not affect your tests.
If you’re running an e2e testing environment and loading smartserve.js
script, you might want to take extra steps to ensure that Qubit Experiences do not alter your wrapped components in unexpected ways.
There are two ways to achieve this:
-
Firstly, you can append the following query parameters to your URL
?qb_opts=remember&qb_experiences=-1
. This drops a cookieqb_opts
, which persists the setting for the rest of the session -
Alternatively, drop a cookie in your server side or client side code. The cookie key is
qb_opts
and value is%7B%22experiences%22%3A%5B-1%5D%7D
. Here’s example JavaScript that drops the cookie:
document.cookie = 'qb_opts=' + encodeURIComponent(JSON.stringify({"experiences":[-1]})) + "; path=/"
The experiences
option is typically used to force execution of a specific Qubit Experience.
Specifying -1
signals that nothing should be executed, which keeps your testing environment clear of Experiences.