Bringing the best of React to TV navigation

A React-Like Solution for Remote Control

About us

Pierre Poupin

github.com/pierpo

Mathieu Fedrigo

twitter.com/mat_fdg

Context: Smart TVs

A simple TV App

Our goal

Our goal

Android codebase

iOS codebase

Web codebase

Our goal

React multiplatform codebase

Thanks React Native! Classic!

Our goal

press "right"

But... How do I handle spatial navigation with React Native?

How do I handle spatial navigation?

Native TV OS components?

❌ Not multiplatform...
❌ Not available on the web!

Custom navigation system in React?

✅ Multiplatform
🤯 but it's a hard problem!

Why is it hard?

rows, grids, nested grids, an element disappears, the focused element disappears, a modal appears...

Many edge cases of "where am I when [something] happens?"

  • The expressive API we should have
  • How we handled all this logic
  • How we plugged this all to React!

We will cover:

Keeping track of the positions of the elements in React 

🗺️

🧭

A hard problem with many solutions

  • We had a very imperative logic 🪖
    • Manual spatial registering of components
    • ...in duplication of the React JSX declarations
  • Which lead to a very leaky abstraction 🚰

First iteration: we faced dead-ends!

A hard problem with many solutions

  • Result of the first iteration:
    • 🪲 Lots of bugs
    • 🐰 No dynamic updates (add/remove elements)
    • 🌀 Layout could not be too deeply nested

First iteration: we faced dead-ends!

What we want to do

The declarative API

The declarative API

Basic Program

The declarative API

Focusable Program

The declarative API

Vertical container

The declarative API

Horizontal container → Row

The declarative API

Whole page

The Spatial Navigation problem

Spatial Navigation rules

React

The Spatial Navigation problem

Spatial Navigation rules

React

externalize the navigation logic!

The Spatial Navigation problem

Is there a lib for that? 📚️

github.com/bbc/lrud

LRUD = left-right-up-down

How LRUD works

Represent a spatial layout with a data structure

It's UI agnostic! 🦴

// not the exact structure, but global idea
{
  root: {
    id: 'root-element',
    orientation: 'vertical',
    children: [
      { id: 'row1', orientation: 'horizontal', children: [
        {id: 'program-1-1', isFocusable: true }
      ]},
      { id: 'row2', orientation: 'horizontal', children: [
        {id: 'program-2-1', isFocusable: true }
      ]}
    ]
  }
}

Represent a spatial layout with a data structure

It's UI agnostic! 🦴

How LRUD works

LRUD's representation is actually a tree!

(just like React 😎 it's a match! ❤️‍🔥)

How LRUD works

LRUD: Summary

Register the root

lrud.registerElement('root', { direction: 'vertical' })

Register a row

lrud.registerElement('root', { direction: 'vertical' })
lrud.registerElement('row1', { parent: 'root', direction: 'horizontal' })

Register programs

lrud.registerElement('root', { direction: 'vertical' })
lrud.registerElement('row1', { parent: 'root', direction: 'horizontal' })
lrud.registerElement('program-1', { parent: 'row1', isFocusable: true })
lrud.registerElement('program-2', { parent: 'row1', isFocusable: true })

Press right!

lrud.registerElement('root', { direction: 'vertical' })
lrud.registerElement('row1', { parent: 'root', direction: 'horizontal' })
lrud.registerElement('program-1', { parent: 'row1', isFocusable: true })
lrud.registerElement('program-2', { parent: 'row1', isFocusable: true })
lrud.handleKeyEvent('right') // actually, any key to get first focus
lrud.registerElement('root', { direction: 'vertical' })
lrud.registerElement('row1', { parent: 'root', direction: 'horizontal' })
lrud.registerElement('program-1', { parent: 'row1', isFocusable: true })
lrud.registerElement('program-2', { parent: 'row1', isFocusable: true })
lrud.handleKeyEvent('right')
lrud.handleKeyEvent('right')

Press right (again)!

LRUD + React

a love story with many chapters

2 trees that need to live together

React 

LRUD

LRUD setup

Empty node

Register Node

Basic Context

Basic Context

Nested Context

Nested Context

Nested Context

Give id to children

Get parentId

Does it work?

 

LRUD ERROR: Cannot find "row1"

 

Render order

Logs order:

Logs order:

Render order

Tree traversal is

depth-first, pre-order (NLR)

 

Node → left path → right path

 

Credit: skilled.dev

Logs order:

Render order

Logs order:

Render order

Node → left path → right path

Logs order:

Render order

Node → left path → right path

Logs order:

Render order

Node → left path → right path

Logs order:

Render order

Node → left path → right path

Logs order:

Render order

Node → left path → right path

Logs order:

Render order

Node → left path → right path

Logs order:

Render order

Node → left path → right path

Logs order:

Render order

Node → left path → right path

useEffect order

Logs order:

Logs order:

useEffect order

Tree traversal is

depth-first, post-order (LRN)

 

left path → right path → Node

 

Credit: skilled.dev

Logs order:

useEffect order

Logs order:

Render order

left path → right path → Node

Logs order:

Render order

left path → right path → Node

Logs order:

Render order

left path → right path → Node

Logs order:

Render order

left path → right path → Node

Logs order:

Render order

left path → right path → Node

Logs order:

Render order

left path → right path → Node

Logs order:

Render order

left path → right path → Node

Logs order:

Render order

left path → right path → Node

Logs order:

Render order

left path → right path → Node

Logs order:

Render order

left path → right path → Node

Logs order:

Render order

left path → right path → Node

Logs order:

Render order

left path → right path → Node

Logs order:

Render order

left path → right path → Node

Logs order:

Render order

left path → right path → Node

Recap

Order useEffect

Order render

LRUD ERROR: Cannot find "row1"

A custom useEffect-ish

Basic

Basic

⚠️ don't try this at home

Basic

Dependencies

Destructor

Unmount

Fix registration order

Does it work?

Nothing happens?

Focused state

Pass focused state

Pass focused state

*That's not it

  • Handling the scroll
  • Handle pages on top of each other
  • Side menu that is common between pages
  • Navigation in virtualized lists
  • Refreshing virtualized lists with new data
  • Handling the default focus

We had many other challenges to tackle

The React-way = ❤️

Adopting a React-friendly declarative API was such a huge productivity boost

Thank you!

Check out https://blog.bam.tech !

Made with Slides.com