Wistia component wrappers for React
Wistia provides embed libraries that decorate DOM nodes based on selectors. This
traditional, jQuery-style strategy presents the least friction for the majority
of our users (most of whom have static sites), but it doesn’t always play nicely
out of the box with SPAs.
This library is not a reimplementation of the Wistia player and other “embeds”
in React. The player, channels, etc are still managed by the usual Wistia
libraries. Rather this is a glue layer to help ensure correct behavior in the
context of a React application. It aims to:
- load the correct scripts (only if needed, and only once)
- correctly key vdom nodes so that they destructively rerenders when appropriate
- prevent race conditions when hydration is in play
- be compatible with server-side rendering
It’s the last of these which is most notable. The first few wouldn’t be hard for
the typical React developer to devise ad hoc, but the SSR part requires some
knowledge of APIs used by the Wistia player. Also, because it’s happening on the
backend, this functionality goes beyond what the Wistia embeds on their own can
provide.
Using @wistia/react-embeds
in an app that does universal rendering, you can get your
video-related SEO data into the server-rendered HTML, which means scrapers that
don’t evaluate JS will still pick the information up. This is particularly
useful for our new “channels” feature where it can improve the quality of link
previews on social media platforms.
Quick Start Guide
npm install @wistia/react-embeds
Rendering a Wistia video
<WistiaProvider>
<WistiaPlayer hashedId="abc123">
</WistiaProvider>
Rendering a Wistia Channel
<WistiaProvider>
<WistiaChannel hashedId="abc123">
</WistiaProvider>
The components
There are presently three components:
<WistiaPlayer>
<WistiaChannel>
<WistiaProvider>
Both the <WistiaPlayer>
and <WistiaChannel>
components can be used for both
ordinary (“inline”) embeds and modal embeds (“popovers”).
The <WistiaProvider>
component should appear once, likely at or near the root
of the app; all other components should be descendants of it. If you’re doing
SSR, you’ll need to provide it with two props: a throw-away context object (it
will be mutated) and the initial href (it does not need to be updated since it
is only used during the SSR initial render).
Using the SEO head elements can be tricky. If you have used certain CSS-in-React
libraries before, the pattern may be familiar, but there’s an extra rub because
extracting the final data is an asynchronous operation.
WistiaPlayer
The <WistiaPlayer>
component takes the same options (as React props) which
one can set using class=
on “raw” wistia embeds. There is a list at the
Embed options support
page. The only difference here is that numbers, booleans, etc are expected to
be JS values of those types rather than strings.
The only required prop is hashedId
, the ID of the video media that will be
embedded.
Some defaults are special-cased to improve ergonomics for React apps:
- Wistia’s “popovers” are video embeds that open in modal overlays when a target
element is clicked. There is an explicit, required
popover
prop/option (true
/false
) as well as an optional popoverContent
prop, but if left
undefined, the default popoverContent
value is thumbnail
. For popovers with popoverContent="link
, the <WistiaPlayer>
component expects to have children.
<WistiaPlayer hashedId="abc123" popover={true} popoverContent="link">
<span>Popover Link</span>
</WistiaPlayer>
- Wistia’s
videoFoam
option is used to make video embeds responsive. It’s
unusual to not want this behavior, so it defaults to true
if it has not been
explicitly defined. if videoFoam
is set to false
, explicit width
and
height
values should be passed in by prop.
<WistiaPlayer
hashedId="abc123"
videoFoam={false}
style={{
width: '640px',
height: '360px',
}}
/>
WistiaChannel
The <WistiaChannel>
component works the same way as the player. This is a new
feature at the time of writing this and the options are not all documented yet.
Aside from hashedId
, the main option of interest is mode
, which controls
whether the channel appears inline or as a preview link card that opens an
overlay.
Requirements
This makes use of the Context API that was introduced in React 16.3.0. Both
react and prop-types are peer dependencies, though the latter is not imported in
the production builds.
A reasonably current ES environment is expected. Polyfills aren’t included here
since this would possibly be redundant or unnecessary depending on your usage.
In the client, URL
, URLSearchParams
, and various ES2015 methods are
employed (e.g. Object.assign
), but we’ve avoided anything that would introduce
dependencies on regenerator runtime or other notoriously finnicky runtime
polyfills so as long as you’re either only targeting modern browsers or are
using @babel/polyfill it should be solid down to IE11. On the node side, we make
use of async functions and ES2019 methods; the expectation is that you’re either
using Node 11 or are using >= 8 but have polyfilled through ES2019.
Implementation notes
Neither <WistiaPlayer>
nor <WistiaChannel>
produce any DOM nodes until
after mount. This isn’t super intuitive and merits some explanation.
The SSR support here refers to (a) general compatibility with SSR and (b) the
special provisions made by <WistiaProvider>
for generating head elements for
SEO and linking, not (c) pre-rendering the child DOM tree normally generated by
the Wistia libraries in the client. This third item would be very hard for us to
pull off at the moment, especially while remaining framework agnostic.
Well, that explains why complete SSR isn’t happening here, but it doesn’t
explain why we don’t even generate the “hook” (the element with the
wistia_embed_xxxx
class) during SSR.
This is because the player lib is loaded “outside” your own application. It
isn’t a normal dependency, and network and caching variations may lead to it
loading before or after your own app’s code. Most often it will load first. If
the player does load first and the selector hook is already present, the player
will begin adding new DOM nodes inside that target element. When your app then
tries to hydrate, React will see the elements added by the player as a
discrepancy and remove them.
In other words, we defer inclusion of the hook until after mount because it’s
the earliest time when we’re certain React’s hydration has taken place, and any
other approach produces race conditions that may lead to visible flicker,
autoplay failure, etc.
We may be able to improve this behavior in time.