Connection-Aware Components

Over the last decade, we have learned to embrace the uncertainty of developing for the web.

We don’t design sites for specific screen dimensions anymore, we make them responsive. We don’t assume ideal browsers and devices, we use progressive enhancement. When it comes to connectivity though, we still treat that as a binary choice: you’re either on- or offline.

Real connections are not that simple. Depending on your location, network condition or data plan, speeds can range from painfully slow to blazingly fast. The concept of “online” can be a drastically different experience for different users, especially on mobile.

What if there was a way to adapt websites based on our users connections, just like we do for varying display widths and browser capabilities? The Network Information API might enable us to do so.

# The Network Information API

This API is an editor’s draft by the WICG and currently available in Chrome. It can be accessed through the read-only property navigator.connection (MDN), which exposes several properties that provide information about a user’s current connection:

  • connection.type:

Returns the physical network type of the user agent as strings like “cellular”, “ethernet” or “wifi”.

  • connection.downlink:

An effective bandwidth estimate (in Mb/s), based on recently observed active connections.

  • connection.rtt:

An estimate of the average round-trip time (in milliseconds), based on recently observed active connections.

  • connection.saveData:

Returns true if the user has requested “reduced data mode” in their browser settings.

  • connection.effectiveType:

This is a combined estimation of the network quality, based on the round-trip time and downlink properties. It returns a string that describes the connection as either: slow-2g, 2g, 3g or 4g. Here’s how these categories are determined:

# Responding to Changes

There is also an Event Listener available on the connection property that fires whenever a change in the network quality is detected:

function onConnectionChange() {
const { rtt, downlink, effectiveType } = navigator.connection
console.log(`Round Trip Time: ${rtt}ms`)
console.log(`Downlink Speed: ${downlink}Mb/s`)
console.log(`Effective Type: ${effectiveType}`)
}
navigator.connection.addEventListener('change', onConnectionChange)

# Support

can I use support table for the network information API

👉 Be aware that all of this is still experimental. Only Chrome and Samsung Internet browsers have currently implemented the API. It’s a very good candidate for progressive enhancement though - and support for other platforms is on the way.

# Connection-aware components

So how could this be used? Knowing about connection quality enables us to custom-fit resources based on network speed and data preferences. This makes it possible to build an interface that dynamically responds to the user’s connection - a “connection-aware” frontend.

By combining the Network Information API with React, we could write a component that renders different elements for different speeds. For example, a <Media /> component in a news article might output:

  • offline: a placeholder with alt text
  • 2g / reduced data mode: a low-resolution image, ~30kb
  • 3g: a high resolution retina image, ~200kb
  • 4g: a HD video ~1.8MB
a media component, showing four different states of an image or video of a chameleon
The different states of our Media component

Here’s a (very simplified) example of how that might work:

class ConnectionAwareMedia extends React.Component (
constructor(props) {
super(props)
this.state = {
connectionType: undefined
}
}

componentWillMount() {
// check connection type before first render.
if (navigator.connection && navigator.connection.effectiveType) {
const connectionType = navigator.onLine
? navigator.connection.effectiveType
: 'offline'
this.setState({
connectionType
})
}
}

render() {
const { connectionType } = this.state
const { imageSrc, videoSrc, alt } = this.props

// fallback if network info API is not supported.
if (!connectionType) {
return <Image src={imageSrc.hires} alt={alt} />
}

// render different subcomponents based on network speed.
switch(connectionType) {
case 'offline':
return <Placeholder caption={alt} />

case '4g':
return <Video src={videoSrc} />

case '3g':
return <Image src={imageSrc.hires} alt={alt} />

default:
return <Image src={imageSrc.lowres} alt={alt} />
}
}
)

# Using a Higher-Order Component

The above example makes our component a bit unpredictable - it renders different things, even when given the same props. This makes it harder to test and maintain. To simplify it and enable reuse of our logic, moving the network condition check into a separate higher-order component might be a good idea.

Such a HoC could take in any component we want and make it connection-aware, injecting the effective connection type as a prop.

function withConnectionType(WrappedComponent, respondToChange = false) {
return class extends React.Component {
constructor(props) {
super(props)
this.state = {
connectionType: undefined
}
// Basic API Support Check.
this.hasNetworkInfoSupport = Boolean(
navigator.connection && navigator.connection.effectiveType
)
this.setConnectionType = this.setConnectionType.bind(this)
}

componentWillMount() {
// Check before the component first renders.
this.setConnectionType()
}

componentDidMount() {
// optional: respond to connectivity changes.
if (respondToChange) {
navigator.connection.addEventListener(
'change',
this.setConnectionType
)
}
}

componentWillUnmount() {
if (respondToChange) {
navigator.connection.removeEventListener(
'change',
this.setConnectionType
)
}
}

getConnectionType() {
const connection = navigator.connection
// check if we're offline first...
if (!navigator.onLine) {
return 'offline'
}
// ...or if reduced data is preferred.
if (connection.saveData) {
return 'saveData'
}
return connection.effectiveType
}

setConnectionType() {
if (this.hasNetworkInfoSupport) {
const connectionType = this.getConnectionType()
this.setState({
connectionType
})
}
}

render() {
// inject the prop into our component.
// default to "undefined" if API is not supported.
return (
<WrappedComponent
connectionType={this.state.connectionType}
{...this.props}
/>

)
}
}
}

// Now we can reuse the function to enhance all kinds of components.
const ConnectionAwareMedia = withConnectionType(Media)

👉 This small proof-of concept is also available on CodePen, if you want to play around.

# Further Reading

Webmentions

What’s this?
  1. Victor OfoegbuVictor Ofoegbu
    Reading this article from @mxbck's blog about the Network Information API. It's been a year since the article was published, but browser support is still discouraging 😒. Cool stuff, but little browser support. mxb.dev/blog/connectio…
  2. Max StoiberMax Stoiber
    Love getting credit for the cool stuff you do hehehe 😈 (going to ping @smashingmag to fix this!)
  3. Stevie J JonesStevie J Jones
    I thought maybe they had attributed it to Max Bøck, Max Bõck or even that scoundrel Max Bôck!!
  4. Max BöckMax Böck
    haha yeah - sometimes people also mistake me for a react wunderkind, so goes both ways 😅
  5. Max BöckMax Böck
    cool guys. we all meet at the annual "my name sounds like MacBook" convention
Show All Webmentions (9)
  1. Will BoydWill Boyd
    oh neat 👍 looks like support is still pretty spotty tho caniuse.com/netinfo
  2. Sonny.cssSonny.css
    the lie-fi concept has been sitting around for some time now, little attention it gets unfortunately. also, `navigator` has plenty more features, similarly, rare to see them IRL developer.mozilla.org/en-US/docs/Web…
  3. Max BöckMax Böck
    Great stuff! I think there's already some progress here with client hints and the Network Information API. Although there hasn't been much development in recent years... mxb.dev/blog/connectio…
  4. Jim NielsenJim Nielsen
    ha i love the web — 2018 you wrote this and i'm only now aware of this API. thx for the link. this definitely gives me more thoughts to stew on...gonna have to work them out in a blog post.