How To Create a Music Player in Reason With The useContext Hook Part 4
Tags: reason, react, learning, tutorial
Published: 2019-10-23 17:51:19

---
title: How To Create a Music Player in Reason With The useContext Hook Part 4
published: true
tags: reasonml, react, learning, tutorial
series: How To Create a Music Player in Reason
canonical_url: https://www.rockyourcode.com/how-to-create-a-music-player-in-reason-with-the-use-context-hook-part-4
---

**UPDATE**:  
ReasonML + BuckleScript is now [Rescript](https://rescript-lang.org/blog/bucklescript-is-rebranding).  
As the ecosystem has changed around those tools, this blog post is not accurate anymore.

---

We've come quite far with our [music player in ReasonReact][demo]. We created a React Context with state, a hook to manage the Context, and we started with our UI.

In this post, we will create a component for the music player control buttons, and we will finally implement the functionality to play the track via the DOM API.

You can find the complete code on [GitHub][repo].

## Create Player Controls

Create `src/PlayerControls.re`:

```reasonml
open ReactUtils;

[@react.component]
let make = () => {
  let (
    playing,
    _trackList,
    currentTrackName,
    pauseTrack,
    _playTrack,
    playPreviousTrack,
    playNextTrack,
  ) =
    UseMusicPlayer.useMusicPlayer();

  <>
    <div className="box controls has-background-grey-dark">
      <div className="current-track has-text-light">
        <marquee> {s(currentTrackName)} </marquee>
      </div>
      <div className="buttons is-centered">
        <button
          className="button has-text-light has-background-grey-dark"
          onClick=playPreviousTrack
          disabled={
            switch (playing) {
            | Playing(_idx) => false
            | NotPlaying => true
            }
          }>
          <i className="fas fa-step-backward" />
        </button>
        <button
          className="button has-text-light has-background-grey-dark"
          onClick={_ => pauseTrack()}
          disabled={
            switch (playing) {
            | Playing(_idx) => false
            | NotPlaying => true
            }
          }>
          {
            switch (playing) {
            | Playing(_idx) => <i className="fas fa-pause" />
            | NotPlaying => <i className="fas fa-play" />
            }
          }
        </button>
        <button
          className="button has-text-light has-background-grey-dark"
          onClick=playNextTrack
          disabled={
            switch (playing) {
            | Playing(_idx) => false
            | NotPlaying => true
            }
          }>
          <i className="fas fa-step-forward" />
        </button>
      </div>
    </div>
  </>;
};
```

There's nothing new here. We create a new component with the `[@react.component]` syntax. Then we load the Context with `MusicPlayer.useMusicPlayer`.  
The JSX contains our HTML markup as well as some `onClick` functions. The logic for those functions lives in `useMusicPlayer`.  
We disable all buttons if we are in the `NotPlaying` state.

Add the component to `src/App.re`:

```reasonml
open ReactUtils;

[@react.component]
let make = () =>
  <div className="section is-fullheignt">
    <div className="container">
      <div className="column is-6 is-offset-4">
        <h1 className="is-size-2 has-text-centered">
          {s("Reason Music Player")}
        </h1>
        <br />
        <MusicPlayer>
          <TrackList />
          <PlayerControls /> // * new *
        </MusicPlayer>
      </div>
    </div>
  </div>;
```

## Let The Music Play!

Everything works now - except there's no music! 🎶

We'll need some audio files to play, and we also need to play the music with [`new Audio()`][htmlaudio].

Like in the [original tutorial][upmostly], I grabbed three random mp3 tracks from [bensound.com][bensound].  
I saved them in the `src` folder.

### Webpack

[Webpack][webpack] will load the mp3 files. Install the `file loader` [plugin](https://webpack.js.org/loaders/file-loader/):

```bash
npm install file-loader --save-dev
```

Modify `webpack.config.js`:

```diff
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const outputDir = path.join(__dirname, 'build/')

const isProd = process.env.NODE_ENV === 'production'

module.exports = {
  entry: './src/Index.bs.js',
  mode: isProd ? 'production' : 'development',
  output: {
    path: outputDir,
    filename: 'Index.js',
  },
+  module: {
+    rules: [
+      {
+        test: /\.mp3$/,
+        loader: 'file-loader',
+      },
+    ],
+  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html',
      favicon: './src/favicon.ico',
      inject: false,
    }),
  ],
  devServer: {
    compress: true,
    contentBase: outputDir,
    port: process.env.PORT || 8000,
    historyApiFallback: true,
  },
}
```

### Interop With The DOM API

We want to create a new [`Audio()` HTML Element][htmlaudio], which can play the music track.

[bs-webapi](https://github.com/reasonml-community/bs-webapi-incubator) is a library that provides bindings to the DOM and other Web APIs.  
Unfortunately, `HTMLAudioElement` is still on the roadmap. That means that we have to write the bindings ourselves.

Create `src/JsAudio.re`:

```reasonml
type audio;
[@bs.new] external make: string => audio = "Audio";
[@bs.send] external play: audio => unit = "play";
[@bs.send] external pause: audio => unit = "pause";
```

We use `[@bs.new]` to initialize a new instance of the HTML Audio Element, see [here](https://bucklescript.github.io/docs/en/interop-cheatsheet#new-instance).
The command creates a `make` function, which takes a string and returns an audio element via the [`external` keyword](https://bucklescript.github.io/docs/en/intro-to-external).

We use `[@bs.send]` for the functions [HTMLMediaElement.play()](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/play) and [HTMLMediaElement.pause()](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/pause), see [here](https://bucklescript.github.io/docs/en/function#object-method).

Now let's adjust our state to include the audio element (`src/SharedTypes.re`):

```reasonml
type state = {
  tracks: musicTracks,
  playing,
  audioPlayer: JsAudio.audio, // * new *
};
```

We now modify `src/MusicPlayer.re`. First, we need to import our mp3 files; then, we add our audio player element to state. We set up the audio player with an empty string.

Import the files with [`[@bs.module]`](https://bucklescript.github.io/docs/en/import-export#import-a-default-value).

```reasonml
[@bs.module "./bensound-summer.mp3"] external summer: string = "default";
[@bs.module "./bensound-ukulele.mp3"] external ukulele: string = "default";
[@bs.module "./bensound-creativeminds.mp3"]
external creativeminds: string = "default";

let initialState: SharedTypes.state = {
  tracks: [|
    {name: "Benjamin Tissot - Summer", file: summer},
    {name: "Benjamin Tissot - Ukulele", file: ukulele},
    {name: "Benjamin Tissot - Creative Minds", file: creativeminds},
  |],
  playing: NotPlaying,
  audioPlayer: JsAudio.(make("")) // * new *
};
```

Note that we use a shorthand syntax to "open" the `JSAudio` module locally, see [the Reason documentation here](https://reasonml.github.io/docs/en/module#open-ing-a-module).

Every time we click on a "play" button, we fire off the `PlayTrack(index)` action (inside `src/UseMusicPlayer.re`). At the same time, we'd like to initialize the [HTML Audio Element][htmlaudio] with the correct file.

```reasonml
/* src/MusicPlayer.re */

// previous code

let withPlayTrack = (state: SharedTypes.state, index) => {
  ...state,
  playing: Playing(index),
  audioPlayer: JsAudio.(make(state.tracks[index].file)), // * new *
};
```

[We open the `JSAudio` module locally](https://reasonml.github.io/docs/en/module#open-ing-a-module), then call the `make` function with the correct file name (which is a string).

Still, the app won't play or pause a track with HTML Audio.

We'll need to employ the [`useEffect` hook](https://reactjs.org/docs/hooks-effect.html) to invoke the "play", and "pause" functions from `JSAudio`:

```reasonml
/* src/MusicPlayer.re */

// previous code

[@react.component]
let make = (~children) => {
  let (state, dispatch) = React.useReducer(reducer, initialState);

  /* new */
  React.useEffect1(
    () => {
      switch (state.playing) {
      | Playing(_idx) => JsAudio.(state.audioPlayer |> play)
      | NotPlaying => JsAudio.(state.audioPlayer |> pause)
      };
      None; // (A)
    },
    [|state.playing|], // (B)
  );

  // JSX here
```

You can read more about ReasonReact's Hooks API on the [ReasonReact documentation website][reasonreactuseeffect].

We have to explicitly state how many dependencies `useEffect` has and use the correct function (i.e., `useEffect0`, `useEffect1`).  
We return `None` from the function (`A`). It won't unmount, but that doesn't matter in our case.  
Alternatively, we could return an unmount function, for example: `Some(() => Js.log("unmount"));`.  
At line `B`, we declare our dependencies.

You can find the complete file on [GitHub](https://github.com/sophiabrandt/reason-music-player/blob/master/src/MusicPlayer.re).

There's still a minor problem. If we play a track and skip to another one, the current track doesn't pause. Fix it in `src/useMusicPlayer.re`:

```reasonml
// previous code

let playTrack = index =>
    switch (playing) {
    | Playing(idx) =>
      index === idx ?
        pauseTrack() :
        {
          JsAudio.(state.audioPlayer |> pause);  // * new *
          MusicPlayer.PlayTrack(index) |> dispatch;
        }
    | NotPlaying => MusicPlayer.PlayTrack(index) |> dispatch
    };

// more code
```

The above code stops the currently playing track with `JSAudio` before it dispatches the new action.

Find the complete file on [GitHub](https://github.com/sophiabrandt/reason-music-player/blob/master/src/UseMusicPlayer.re).

# Recap

In this post, we learned how to import files and how to write [BuckleScript][bs] bindings to use the DOM API with [ReasonReact][reasonreact].  
We applied `useEffect` in ReasonReact to trigger side effects.

During this blog post series, we build a music player app that can use the Web API with ReasonReact and hooks: `useContext`, `useEffect`, `useReducer`.

I hope you had fun, and you learned something about ReasonML and BuckleScript.

Again, many thanks to James King for his [original React.js tutorial][upmostly].

Also, thanks to [Yawar Amin](https://github.com/yawaramin) and [Florian Hammerschmidt](https://github.com/fhammerschmidt) for helping me out with translating the JavaScript code to ReasonML.

You can find the complete code on **[GitHub][repo]**.

### Further Reading

- [How to Use the useContext Hook in React][upmostly] by James King
- [ReasonReact][reasonreact]
- [GitHub Repository for the music player][repo]

[demo]: https://sophiabrandt.github.io/reason-music-player/
[repo]: https://github.com/sophiabrandt/reason-music-player
[upmostly]: https://upmostly.com/tutorials/how-to-use-the-usecontext-hook-in-react
[bensound]: https://www.bensound.com/
[webpack]: https://webpack.js.org/
[htmlaudio]: https://developer.mozilla.org/en-US/docs/Web/API/HTMLAudioElement/Audio
[reasonreactuseeffect]: https://reasonml.github.io/reason-react/docs/en/components#hooks
[reasonreact]: https://reasonml.github.io/reason-react/
[bs]: https://bucklescript.github.io/