How To Create a Music Player in Reason With The useContext Hook Part 3
Tags: reason, react, learning, tutorial
Published: 2019-10-22 12:56:35

---
title: How To Create a Music Player in Reason With The useContext Hook Part 3
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-3
---

**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.

---

Our goal is to create a music player with ReasonReact like this one: **[☞ Demo][demo]**.

James King wrote the JavaScript version of the tutorial at **[upmostly][upmostly]**. My blog post series is a port of the tutorial to ReasonML and [ReasonReact][reasonreact].  
I encourage you to check out the [original JavaScript tutorial][upmostly] if you're unsure about how `useContext` works.

ReasonReact compiles (via [BuckleScript][bs]) to React. We can leverage our existing React knowledge. We have all the power of React, but with the type-safety of OCaml, a mature language.

In **[part 1 of the series](https://dev.to/sophiabrandt/how-to-create-a-music-player-in-reason-with-the-usecontext-hook-part-1-59dj)**, we set up the project.  
In **[part 2 of the series](https://dev.to/sophiabrandt/how-to-create-a-music-player-in-reason-with-the-usecontext-hook-part-2-3f4a)**, we created the `MusicPlayer` component with a React Context.

Find the [code repository on GitHub][repo].


## Manage State And Create a Custom Hook

We need a way to manage our state. The `MusicPlayer` component has a `useReducer` function that we can use.

We want to have the ability to play a track and to pause a track. If we play a track, we have to tell the program which one. We'll use the Array's index for that, as our tracks are in an Array.

`src/MusicPlayer.re`

```reasonml
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,
};

type action =
  | PauseTrack        // (A)
  | PlayTrack(int);

// when we pause a track, we need to transition to
// the `NotPlaying` state
//
let withPauseTrack = state: SharedTypes.state => {
  ...state,
  playing: NotPlaying,                           // (B)
};

// when we play a track, we need to transition to
// the `PlayingState` and add the payload of the
// track's index
//
let withPlayTrack = (state: SharedTypes.state, index) => {
  ...state,
  playing: Playing(index),                      // (B)
};

let reducer = (state: SharedTypes.state, action) =>
  switch (action) {                            // (A)
  | PauseTrack => withPauseTrack(state)
  | PlayTrack(index) => withPlayTrack(state, index)
  };
```

Several interesting things are going on here. Our `action` is another [variant][variant]; thus, we can [pattern-match][patternmatching] on it in the reducer function (`A`).

[Pattern-matching is one of the power features of ReasonML][patternmatching]:

> It's like destructuring, but comes with even more help from the type system.

What happens when you delete the `NotPlaying` line in the reducer (`A`)? Try it out! The compiler will give you a warning:

```bash
Warning 8: this pattern-matching is not exhaustive.
Here is an example of a value that is not matched:
NotPlaying
```

Pattern-matching conveniently resembles JavaScript's [switch syntax](https://devdocs.io/javascript/statements/switch). But thanks to the compiler warnings, you can make sure that you handle all cases.

Furthermore, we [update our immutable records by creating new records with the spread operator](https://reasonml.github.io/docs/en/record#immutable-update) (`B`).  
That also looks like JavaScript!

Now that we know how to handle state, we'll create a custom hook that will manage Context.

Let's see how it would look like in [JavaScript](https://github.com/upmostly/react-use-context-hook/blob/master/src/hooks/useMusicPlayer.js):

```js
import { useContext } from 'react'
import { MusicPlayerContext } from '../MusicPlayerContext'

const useMusicPlayer = () => {
  const [state, setState] = useContext(MusicPlayerContext)

  function playTrack(index) {
    if (index === state.currentTrackIndex) {
      togglePlay()
    } else {
      setState(state => ({
        ...state,
        currentTrackIndex: index,
        isPlaying: true,
      }))
    }
  }

  function togglePlay() {
    setState(state => ({ ...state, isPlaying: !state.isPlaying }))
  }

  function playPreviousTrack() {
    const newIndex =
      (((state.currentTrackIndex + -1) % state.tracks.length) +
        state.tracks.length) %
      state.tracks.length
    playTrack(newIndex)
  }

  function playNextTrack() {
    const newIndex = (state.currentTrackIndex + 1) % state.tracks.length
    playTrack(newIndex)
  }

  return {
    playTrack,
    togglePlay,
    currentTrackName:
      state.currentTrackIndex !== null &&
      state.tracks[state.currentTrackIndex].name,
    trackList: state.tracks,
    isPlaying: state.isPlaying,
    playPreviousTrack,
    playNextTrack,
  }
}

export default useMusicPlayer
```

And now in Reason:  
`src/useMusicPlayer.re`

```reasonml
// a hook is a function
let useMusicPlayer = () => {

  // here we'll load our Context
  // it's the same as in JavaScript
  //
  let (state, dispatch) = React.useContext(MusicPlayer.musicPlayerContext);

  let playing = state.playing;

  let trackList = state.tracks;

  // find the current track name
  // we can pattern-match on our state
  // if we are in the state of `Playing`, then find the name of the
  // index of the tracks Array
  // if we don't play anything, we can't have a name, so we'll use
  // a placeholder string
  // ReasonML can infer types, so we don't have to tell the program
  // that the `currentTrackName` is a string
  //
  let currentTrackName =
    switch (playing) {
    | Playing(idx) => state.tracks[idx].name
    | NotPlaying => "Please choose a track to play"
    };

  // this function dispatches to `MusicPlayer` with the
  // `PauseTrack` action we defined earlier
  //
  let pauseTrack = () => MusicPlayer.PauseTrack |> dispatch;

  // here we dispatch to the `PlayTrack(index)` action we defined
  // in `src/MusicPlayer.re`
  //
  let playTrack = index =>
    switch (playing) {
    | Playing(idx) =>
      index === idx ?
        pauseTrack() :
        {
        // here we use the pipe operator
        // this is the same as
        // dispatch(MusicPlayer.PlayTrack(index))
          MusicPlayer.PlayTrack(index) |> dispatch;  // (A)
        }
    | NotPlaying => MusicPlayer.PlayTrack(index) |> dispatch
    };

  let trackListLength = Array.length(trackList);

  let playPreviousTrack = _ =>
    switch (playing) {
    | Playing(idx) =>
      ((idx - 1) mod trackListLength + trackListLength)
      mod trackListLength
      |> playTrack
    | NotPlaying => ()
    };

  let playNextTrack = _ =>
    switch (playing) {
    | Playing(idx) => (idx + 1) mod trackListLength |> playTrack
    | NotPlaying => ()
    };

  (
    playing,
    trackList,
    currentTrackName,
    pauseTrack,
    playTrack,
    playPreviousTrack,
    playNextTrack,
  );
};
```

I tend to use the pipe operator (`|>`) to chain functions (see line `A`).

The current ReasonML documentation is a bit [sparse](https://reasonml.github.io/api/Pervasives.html):

> let `(|>)`: `('a, 'a => 'b) => 'b;`  
> Reverse-application operator: `x |> f |> g` is exactly equivalent to `g (f (x))`.

## Creating the UI

We now have a `MusicPlayer` component that contains the React context (including state and a dispatch function to handle state transitions) and a custom `useMusicPlayer` hook.

Let's update `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 /> </MusicPlayer>// * new *
      </div>
    </div>
  </div>;
```

Create `src/TrackList.re`:

```reasonml
open ReactUtils;

[@react.component]         // (A)
let make = () => {
  let (
    playing,
    trackList,
    _currentTrackName,
    _pauseTrack,
    playTrack,
    _playPreviousTrack,
    _playNextTrack,
  ) =
    UseMusicPlayer.useMusicPlayer();  // (B)

  <>
    {
      Array.mapi(                     // (C)
        (index, track: SharedTypes.musicTrack) =>            // (D)
          <div className="box" key={index |> string_of_int}> // (E)
            <div className="columns is-vcentered">
              <button className="button"
               onClick={_ => playTrack(index)}> // (F)
                {
                  switch (playing) {            // (G)
                  | Playing(idx) =>
                    idx === index ?
                      <i className="fas fa-pause" /> :
                      <i className="fas fa-play" />
                  | NotPlaying => <i className="fas fa-play" />
                  }
                }
              </button>
              <div className="song-title column">
                {s(track.name)}
              </div>
            </div>
          </div>,
        trackList,
      )
      |> React.array   // (H)
    }
  </>;
};
```

First, we create a new React component (`A`). Then we use our custom hook to gain access to the state and the functions that control the app's state.  
We don't need everything we've exported from `useMusicPlayer`. Thus we can append an underscore under the variables we don't use (`B`).

Similar to React.js, we map over the collection (Array) of our tracks. We use ReasonML's `Array.mapi` to map with an index (`C`). `Array.mapi` takes a function first and the collection as the second parameter.  
Unfortunately, that means that we have to tell Reason the type of `musicTrack` (see line `D`).

We also have to convert the Array's index (an integer) to a string (`D`). Luckily, if you forget that, the compiler will help you out and throw an error.

When we click the button, we want to fire off the `playTrack` function from the custom `useMusicPlayer` hook (`F`).

On line `G`, we decide what kind of button to show. If we don't play any track, then show a "play" button. But if we play a track, we only want to show the "play" button for those tracks that aren't currently playing. For the playing track, we want to show a "pause" button.  
We use [Bulma][bulma] and [FontAwesome icons][fontawesome] for the icons.

Lastly, we have to convert the Reason Array into a React.array (`H`).

## Recap

We created a custom hook and also added state management to our application.  
We started fleshing out the UI with the `TrackList` component.

## Coming Next

In the next posts, we'll create a component for controlling the music player. We'll also create an [HTML Audio Element](https://developer.mozilla.org/en-US/docs/Web/API/HTMLAudioElement) that will allow us to play the mp3 file.

### 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
[reasonreact]: https://reasonml.github.io/reason-react/
[variant]: https://reasonml.github.io/docs/en/variant
[bs]: https://bucklescript.github.io/en/
[patternmatching]: https://reasonml.github.io/docs/en/pattern-matching#nested-patterns
[bulma]: https://bulma.io
[fontawesome]: https://fontawesome.com/start