Remote Control An MDX Deck Presentation
Tags: mdx, react, websockets, hapi
Published: 2019-07-13 03:43:33

---
title: Remote Control An MDX Deck Presentation
published: true
description: How to setup an MDX Deck and then control it from another page.
tags: mdx, react, websockets, hapi
---

# Remote Control An MDX Deck Presentation 
MDX Deck is a great way to create presentation slides however I find it useful to be able to advance the slides when I have stepped away from my computer. I looked to see if this was a solved problem, however I did not find anything. So I then went about looking into how I could do that.

## MDX Deck API

First I had to determine if MDX Deck even provided a way to programmatically move the slides and if so where.

My first attempt involved trying to use the `Head` component in `@mdx-deck/components` however it did not provide access to the 

Fortunately the [Custom Provider](https://github.com/jxnblk/mdx-deck/blob/master/docs/advanced.md#custom-provider-component) looked like the place to go.

```jsx
import React from 'react'
// custom provider with content that appears on every slide
const Provider = props => (
  <div>
    {props.children}
    <div
      css={{
        position: 'fixed',
        right: 0,
        bottom: 0,
        margin: 16,
      }}
    >
      Put your name here
    </div>
  </div>
)

export default {
  Provider,
}
// https://codesandbox.io/s/github/jxnblk/mdx-deck/tree/master/examples/provider
```

The most important part is that the props passed into the provider include the `next`, `previous`, and `goto` functions I required to control the slides. 

First I added a remote control component that will do the work


```jsx
import React from 'react'

export default ({ next, previous, goto }) => {
  return null
}
```

And then I added the control into the provider


```jsx
import React from 'react'
import RemoteControl from './remote-control.js'
const Provider = props => (
  <div>
    {props.children}
    <RemoteControl 
    	next={props.next} 
    	previous={props.previous}
    	goto={props.goto} />
  </div>
)
export default {
  Provider,
}

```

But then how to let the remote control component know when to call next or previous? That's where WebSockets come in. WebSockets allow the web page to receive messages from a server. So it's time to setup a server that supports WebSockets. In this case I will be using [Hapi](https://hapijs.com) and [Nes](https://github.com/hapijs/nes).

In the setup we will need a route to call to invoke the next command and support the WebSocket subscription.

```javascript
const Hapi = require('@hapi/hapi')
const Nes = require('@hapi/nes')
const init = async () => {
  const server = Hapi.server({
    port: 8080,
  })
  await server.register(Nes)
  server.subscription('/slides/{id}')
  server.route({
    method: 'GET',
    path: '/',
    handler: (request, h) => {
      return 'Hello World!!!'
    },
  })
  server.route({
    method: 'GET',
    path: '/slide/{deck}/next',
    config: {
      handler: (r, h) => {
        r.server.publish(`/slides/${r.params.deck}`, {
          action: 'next',
        })
        return { result: 'SENT' }
      },
    },
  })
  await server.start()
  console.log('Server running on %s', server.info.uri)
}

process.on('unhandledRejection', err => {
  console.log(err)
  process.exit(1)
})

init()
```

[View On CodeSandbox](https://codesandbox.io/embed/hapi-nes-next-route-4yeq0)

This creates a Hapi web server with the Nes plugin installed, the subscriptions on the `/slides/{id}` endpoint and a route `/slide/{deck}/next` that when hit calls the subscription passing a message with the action of next.

With that setup it's back to the deck to connect to the server to get the messages and control the slides.

In order to do this I will be using a React Hook from [@brightleaf/react-hooks](https://brightleaf.dev/hooks) as it has a `useNes` hook included.


```jsx
import React from 'react'
import { useNes } from '@brightleaf/react-hooks/lib/use-nes'
export default ({ next, previous, goto }) => {

  const { message, error, connecting, connected, client } = useNes(
    'wss://url-goes-here', false
  )

  const handler = function(update, flags) {
    if(update && update.action === 'next') {
      next()
    }
    if(update && update.action === 'previous') {
      previous()
    }
    if(update && update.action === 'goto') {
      goto(update.slide)
    }
  }
  client.subscribe('/slides/slide-deck-1', handler)

  return null
}
```

This uses a react hook that returns the nes client that then subscribes to the broadcast endpoint and when a message is received the handler checks the action property of the message and performs the action requested.


So if you go to `https://4yeq0.sse.codesandbox.io/slide/slide-deck-1/next` you will see the deck advance a slide.

You can see the slide deck [here](https://codesandbox.io/embed/mdx-deckbasic-template-5mh86) and the server [here](https://codesandbox.io/embed/hapi-nes-next-route-4yeq0)

Now with the mechanics sorted for moving the slides, it's time to put a UI together that can be used. 

```jsx
import React, { useState } from "react";
import ReactDOM from "react-dom";
import { useGet } from "@brightleaf/react-hooks/lib/use-get";
import {
  Button, Column, Columns, Container, Control, Hero,
  HeroBody, Section, Title, SubTitle
} from "@brightleaf/elements";
import { Form, TextInput } from "react-form-elements";
import "./styles.css";

function App() {
  const [deck, setDeck] = useState("slide-deck-1");
  const { getUrl: getNext } = useGet(
    `https://4yeq0.sse.codesandbox.io/slide/${deck}/next`
  );
  const { getUrl: getPrevious } = useGet(
    `https://4yeq0.sse.codesandbox.io/slide/${deck}/previous`
  );
  return (
    <>
      <Hero isBold isInfo>
        <HeroBody>
          <Title>Remote Control</Title>
          <SubTitle>Press the buttons to see some magic happen!</SubTitle>
        </HeroBody>
      </Hero>
      <Section className="App">
        <Container>
          <Columns>
            <Column isHalf>
              <Button
                isPrimary
                isLarge
                isFullWidth
                className="is-tall"
                onClick={e => {
                  e.preventDefault();
                  getPrevious(
                    `https://4yeq0.sse.codesandbox.io/slide/${deck}/previous`
                  );
                }}
              >
                &lt;&lt; Previous
              </Button>
            </Column>
            <Column isHalf>
              <Button
                isPrimary
                isLarge
                isFullWidth
                className="is-tall"
                onClick={e => {
                  e.preventDefault();
                  console.log("click next");
                  getNext(
                    `https://4yeq0.sse.codesandbox.io/slide/${deck}/next`
                  );
                }}
              >
                Next &gt;&gt;
              </Button>
            </Column>
          </Columns>
          <hr />
          <Columns>
            <Column isFull>
              <Form
                name="slidepicker"
                onSubmit={data => {
                  setDeck(data.slides);
                }}
              >
                <TextInput
                  className="field control"
                  labelClassName="label is-large"
                  inputClassName="input is-large"
                  name="slides"
                  initialValue=""
                  label="Slide Deck"
                />
                <Control>
                  <Button isInfo>Connect Slide Deck</Button>
                </Control>
              </Form>
            </Column>
          </Columns>
        </Container>
      </Section>
    </>
  );
}
```

And in action

{% youtube NtYc1XMkeBQ %}

In addition to the codesandbox links in the article, the code that inspired the post can be found on GitHub.

* [API Server](https://github.com/Kevnz/kpi)
* [Slide Deck](https://github.com/Kevnz/intro-to-web-components)
* [Remote UI](https://github.com/Kevnz/remote-control-ui)
* [Brightleaf React Hooks](https://github.com/brightleaf/react-hooks)
* [Brightleaf Elements](https://github.com/brightleaf/elements)