Optional chaining in the ~real world (React video chat app)
Tags: react, webdev, javascript, beginners
Published: 2021-04-29 03:33:14

I started learning JavaScript in 2019, around the time that optional chaining became a thing. 

> The optional chaining operator (`?.`) enables you to read the value of a property located deep within a chain of connected objects without having to check that each reference in the chain is valid. [(MDN)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining)

I remember hearing the hearsay about why this was awesome, but, at the time, the above explanation [and others](https://dev.to/laurieontech/optional-chaining-has-arrived-111l) and any conversations about a question mark in javascript that isn't the ternary operator still went a bit over my head. Fast forward two years, and I've finally run into optional chaining in the ~real world. 

![Gif of Real World cast reads I thought I was being punk'd](https://media.giphy.com/media/klGl9A6AkjKL9I8piK/giphy.gif)

This post shares that encounter! I'll go over video chat participant "tracks" at the highest level, and then walk through why optional chaining makes sense in this use case. 

> _Before diving in, a quick thank you to my colleague Jess for the lesson that became this post. I'm lucky to learn from her at work everyday and [on Twitter](https://twitter.com/gemontracks) too!_ 

## Video chat participant tracks
Like a lot of people, I've been on a lot of video calls this year. I also work at Daily, where my colleagues build [real-time audio and video APIs](https://www.daily.co/?utm_source=dev&utm_campaign=optional-chaining). I write [documentation](https://docs.daily.co/reference#using-the-dailyco-front-end-library?utm_source=dev&utm_campaign=optional-chaining) for the tools they build and prototype [demo apps](https://github.com/daily-demos?utm_source=dev&utm_campaign=optional-chaining), so I'm learning a fair amount about the different moving parts behind video and [audio-only](https://www.daily.co/blog/how-to-build-a-billion-dollar-audio-app-in-a-weekend/?utm_source=dev&utm_campaign=optional-chaining) calls, things I didn't really think about before. 

Take, for example, tracks! 

![Runners on a track](https://media.giphy.com/media/kyj9z12kYr9fRr9T8X/giphy.gif)

When I join a video call with someone else, I and that other person or people trade audio, video, and sometimes screen [media tracks](https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack) back and forth. 

As you've probably experienced, participants' tracks can go through *many* states. Tracks load as participants join, and then they're playable; they can be muted intentionally or because of a disruption. The [Daily API](https://www.daily.co/?utm_source=dev&utm_campaign=optional-chaining) accounts for the following participant track states, for example:

- blocked
- off
- sendable
- loading
- playable
- interrupted

We can find a track's state on the [Daily participants object](https://docs.daily.co/reference#%EF%B8%8F-participants?utm_source=dev&utm_campaign=optional-chaining). The object's keys are session id's for each participant, and the corresponding values include lots of details about the participant. For example, here's the participant object for a session_id `"e20b7ead-54c3-459e-800a-ca4f21882f2f"`: 

```json
"e20b7ead-54c3-459e-800a-ca4f21882f2f": {
    user_id: "e20b7ead-54c3-459e-800a-ca4f21882f2f",
    audio: true,
    video: false,
    screen: false,
    joined_at: Date(2019-04-30T00:06:32.485Z),
    local: false,
    owner: false,
    session_id: "e20b7ead-54c3-459e-800a-ca4f21882f2f",
    user_name: ""
    tracks: {
      audio: {
        subscribed: boolean,
        state: 'playable',
        blocked?: {
          byDeviceMissing?: boolean,
          byPermissions?: boolean
        },
        off?: {
          byUser?: boolean,
          byBandwidth?: boolean 
        },
        track?: <MediaStreamTrack>
      }
      video: { /* same as above */ },
      screenAudio: { /* same as above */ },
      screenVideo: { /* same as above */ },
    }
  }
}
```

The track's state is deeply nested at `participant.tracks.track.state`, where track stands for the kind of track (audio, video, screenAudio or screenVideo). 

And this is where optional chaining comes in. 

## Opting into optional chaining
In JavaScript, if an object doesn't exist, trying to access values on that object throws an error. 

![Mean Girls Lindsay Lohan meme text reads the object does not exist](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mvgtvgeuiv2h839v1x8o.png)

This can be inconvenient when a value we need is deeply nested, like the participant's video/audio track state. Let's look at an example. 

When a participant leaves a call, their audio/video tracks stop. When their audio/video tracks stop, we want to remove their participant tile from the call.

We handle this update the same way we handle all participant updates. I wrote a longer post about how [React hooks help us manage state in this video chat app](https://dev.to/kimberleejohnson/learn-react-hooks-by-looking-under-the-hood-of-a-video-chat-app-3a5o), but tl; dr: the useEffect hook listens for changes to `participantUpdated` state, and on that change updates the rendered `participants` list.

`participantUpdated` stores a string including the name of the event, that participant's session id, and the time the event happened. When a participant's tracks stop, as for other events, we call `setParticipantUpdated` to change the string. Here's how that looks without optional chaining: 

```javascript
const handleTrackStopped = useCallback((e) => {
  logDailyEvent(e);
  setParticipantUpdated(
`track-stopped-${e.participant.user_id}-${Date.now()}`
  );
}, []);
```

Can you guess why this might cause a problem? 

![Schitts Creek Moira Rose gif reads and that's a problem because](https://media.giphy.com/media/1xmBLaMM6ciAYvuNCG/giphy.gif)

Because when a participant leaves a call and their tracks stop, they're no longer a meeting participant. They can't be found on the Daily participants object. `.participant` does not exist. The console throws an error, `Cannot read property 'user_id' of null`: 

![Console error reads TypeError and cannot read property user id of null](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s6qj2jlmjocg2f6fdhwv.png)

From a UI perspective, a black, trackless tile remains even after the participant leaves. This is because `setParticipantUpdated` can't fire, so the hook listening for the change doesn't update the rendered participant list to remove the absent participant, even though their tracks disappear. 

![Video chat app with audio muted and empty black tile](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8jzrfqqgg3gwy3tnvix7.png)

Optional chaining helps us avoid this. Let's add the syntax to `handleTrackStopped`: 

```javascript
const handleTrackStopped = useCallback((e) => {
  logDailyEvent(e);
  setParticipantUpdated(
`track-stopped-${e?.participant?.user_id}-${Date.now()}`
  );
}, []);
```

Now, those `.?` evaluate the missing `.participant` as undefined. If I add a `console.log()` to `handleTrackStopped` to see the string passed to state, that's confirmed: 

![Console reads track-stopped-undefined](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7irgvwxus8gbt7wirr7x.png)

With this successful change to `participantUpdated` state, our hook can register the change, update the participant list, and be sure to remove any trackless tiles. 

## Remember, it's all optional 
Optional chaining makes sense in this [demo video chat app](https://github.com/daily-demos/call-object-react?utm_source=dev&utm_campaign=optional-chaining) for a few reasons. For one thing, our track state data was pretty deeply nested. For another, it's okay if the `.participant` doesn't exist in our app after they leave (we won't be trying to access their data again once they're gone). 

We didn't use optional chaining as our default syntax for every nested object in our app, and it's unlikely that would ever be a good idea. If you're using this syntax in the ~real world, be sure to be explicit about it. 

And, if you are using optional chaining, please tell me about it! When have you opted for it recently? Let me know in the comments or [over on Twitter](https://twitter.com/kimeejohnson).