How to Use the Optional Chaining Operator in Your React App Right Now
Tags: react, javascript, babel, webdev
Published: 2020-01-08 03:08:28

**Subscribe to my email list now at http://jauyeung.net/subscribe/**

**Follow me on Twitter at https://twitter.com/AuMayeung**

**Many more articles at https://medium.com/@hohanga**

Optional chaining is a proposed feature that may be incorporated into the JavaScript specification.

The operator allows you to traverse through a nested object to get the value of variables without worrying if any of those will be undefined.

For example, without optional chaining, if you have the object below:

```javascript
const person = {  
  name: 'Alice',  
  cat: {  
    name: 'Bob'  
  }  
};
```

If you want to get the cat’s name, you have to use the code below:

```javascript
const catName = person.cat.name;
```

If `cat` is undefined or null in `person`, the JavaScript interpreter will throw an error. With the optional chaining operator, you can write:

```javascript
const catName = person?.cat?.name;
```

If `cat` is undefined, the `catName` will be `null`.

It also works with keys of an object. Instead of `const catName = person?.cat?.name;`, we can write:

```
const catName = person?.['cat']?.['name'];
```

This syntax also works with functions. For example, you can write:

```javascript
func?.('foo')
```

To call the function `func` with a string, where `func` may be undefined or null. If the function does not exist, it will not be run.

To further illustrate the example and to show you how to use it in a real application, we will build a React app that uses the [NASA API](https://api.nasa.gov/index.html), to get the latest asteroid data.

We will use the [Create React App](https://github.com/facebook/create-react-app) CLI program to build the app.

As optional chaining is just a proposed feature, it is not currently supported with the CLI, so we have to do some work ourselves by installing some packages and making some changes to the [Babel](https://babeljs.io/) configuration of the app to enable optional chaining.

To start, we run `npx create-react-app nasa-app` to create the project folder with the initial files.

Next, we install `npm i -D @babel/plugin-proposal-optional-chaining customize-cra react-app-rewired` to begin customizing Create React App to support the optional chaining syntax.

Next, we have to add new configuration files and edit existing ones to let our app run and build with the syntax.

First, we add a file called `config-overrides.js` and add the following:

```javascript
const { useBabelRc, override, useEslintRc } = require("customize-cra");
module.exports = override(useBabelRc());
```

To let us use the `.babelrc` configuration file.

Then, we create the `.babelrc` file in the root folder of our project and add:

```javascript
{  
    "plugins": [  
        [  
            "@babel/plugin-proposal-optional-chaining"  
        ],  
    ]  
}
```

This will add support for optional chaining syntax in our project.

Next, we have to switch to make our app run and build with `react-app-rewired` instead of the usual `react-script` program.

To do this, in the `scripts` section of `package.json`, we put:

```javascript
"scripts": {  
    "start": "react-app-rewired start",  
    "build": "react-app-rewired build",  
    "test": "react-app-rewired test --env=jsdom",  
    "eject": "react-scripts eject"  
}
```

In here, we replaced the original scripts that use `react-script` to run and build our app, with `react-app-rewired`.

Now we can use the optional chaining syntax to build our app.

First, we have to install some packages.

Run `npm i axios bootstrap react-bootstrap formik yup react-router-dom` to install the [axios](https://github.com/axios/axios) HTTP client, [React Bootstrap](https://react-bootstrap.github.io/) for styling, [Formik](https://github.com/jaredpalmer/formik) and [Yup](https://www.npmjs.com/package/yup) for building forms and adding form validation, and [React Router](https://reacttraining.com/react-router/) for routing URLs to the pages we build.

Now we can write some code. In `App.js`, we replace the existing code with:

```jsx
import React from "react";  
import { Router, Route } from "react-router-dom";  
import HomePage from "./HomePage";  
import AsteroidsSearchPage from "./AsteroidsSearchPage";  
import { createBrowserHistory as createHistory } from "history";  
import "./App.css";  
import TopBar from "./TopBar";  
const history = createHistory();function App() {  
  return (  
    <div className="App">  
      <Router history={history}>  
        <TopBar />  
        <Route path="/" exact component={HomePage} />  
        <Route path="/search" exact component={AsteroidsSearchPage} />  
      </Router>  
    </div>  
  );  
}export default App;
```

So that we get client-side routing to our pages. In `App.css`, we replace the existing code with:

```css
.center {  
  text-align: center;  
}
```

Next, we start building new pages. Create a file called `AsteroidSearchPage.js` in the `src` folder and add:

```jsx
import React, { useState, useEffect } from "react";  
import { Formik } from "formik";  
import Form from "react-bootstrap/Form";  
import Col from "react-bootstrap/Col";  
import Button from "react-bootstrap/Button";  
import * as yup from "yup";  
import Card from "react-bootstrap/Card";  
import "./AsteroidsSearchPage.css";  
import { searchFeed } from "./requests";
const schema = yup.object({  
  startDate: yup  
    .string()  
    .required("Start date is required")  
    .matches(  
      /([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))/,  
      "Invalid start date"  
    ),  
  endDate: yup  
    .string()  
    .required("End date is required")  
    .matches(  
      /([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|\[12]\d|3\[01]))/,  
      "Invalid end date"  
    ),  
});

function AsteroidsSearchPage() {  
  const [feed, setFeed] = useState({});  
  const [error, setError] = useState("");
  const handleSubmit = async evt => {  
    const isValid = await schema.validate(evt);  
    if (!isValid) {  
      return;  
    }  
    try {  
      const response = await searchFeed(evt);  
      setFeed(response.data.near_earth_objects);  
    } catch (ex) {  
      alert(ex?.response?.data?.error_message);  
    }  
  };

  return (  
    <div className="AsteroidsSearchPage">  
      <h1 className="center">Search Asteroids</h1>  
      <Formik validationSchema={schema} onSubmit={handleSubmit}>  
        {({  
          handleSubmit,  
          handleChange,  
          handleBlur,  
          values,  
          touched,  
          isInvalid,  
          errors,  
        }) => (  
          <Form noValidate onSubmit={handleSubmit}>  
            <Form.Row>  
              <Form.Group as={Col} md="12" controlId="startDate">  
                <Form.Label>Start Date</Form.Label>  
                <Form.Control  
                  type="text"  
                  name="startDate"  
                  placeholder="YYYY-MM-DD"  
                  value={values.startDate || ""}  
                  onChange={handleChange}  
                  isInvalid={touched.startDate && errors.startDate}  
                />  
                <Form.Control.Feedback type="invalid">  
                  {errors.startDate}  
                </Form.Control.Feedback>  
              </Form.Group>  
              <Form.Group as={Col} md="12" controlId="endDate">  
                <Form.Label>End Date</Form.Label>  
                <Form.Control  
                  type="text"  
                  name="endDate"  
                  placeholder="YYYY-MM-DD"  
                  value={values.endDate || ""}  
                  onChange={handleChange}  
                  isInvalid={touched.startDate && errors.endDate}  
                /><Form.Control.Feedback type="invalid">  
                  {errors.endDate}  
                </Form.Control.Feedback>  
              </Form.Group>  
            </Form.Row>  
            <Button type="submit" style={{ marginRight: "10px" }}>  
              Search  
            </Button>  
          </Form>  
        )}  
      </Formik>  
      {Object.keys(feed)?.map(key => {  
        return (  
          <Card style={{ width: "90vw", margin: "0 auto" }}>  
            <Card.Body>  
              <Card.Title>{key}</Card.Title>  
              {feed?.[key]?.length > 0  
                ? feed?.[key]?.map(f => {  
                    return (  
                      <div style={{ paddingBottom: "10px" }}>  
                        {f?.close_approach_data?.length > 0 ? (  
                          <div>  
                            <b>  
                              Close Approach Date:  
                              {f?.close_approach_data?.map(d => {  
                                return <p>{d?.close_approach_date_full}</p>;  
                              })}  
                            </b>  
                          </div>  
                        ) : null}  
                        <div>  
                          Minimum Estimated Diameter:{" "}  
                          {  
                            f?.estimated_diameter?.kilometers  
                              ?.estimated_diameter_min  
                          }{" "}  
                          km  
                        </div>  
                        <div>  
                          Maximum Estimated Diameter:{" "}  
                          {  
                            f?.estimated_diameter?.kilometers  
                              ?.estimated_diameter_max  
                          }{" "}  
                          km  
                          <br />  
                        </div>  
                      </div>  
                    );  
                  })  
                : null}  
            </Card.Body>  
          </Card>  
        );  
      })}  
    </div>  
  );  
}

export default AsteroidsSearchPage;
```

This is where we add a form to search for asteroid data from the NASA API by date range. Both the start and end date fields should be in YYYY-MM-DD format and we changed our form validation to match that in the `schema` object.

Once validation is done, by calling the `schema.validate` function, we search. The response has many nested objects, so we use the optional chaining syntax everywhere in the result cards.

We loop through `close_approach_data` array, and we don’t assume it’s always defined or has a greater than zero length, the same goes for the call to the `map` function. We do not assume that the `map` function is always defined.

We also use the optional chaining syntax for `f?.estimated_diameter?.kilometers  
?.estimated_diameter_min` and `f?.estimated_diameter?.kilometers  
?.estimated_diameter_max`.

The more levels of nesting there are, the less likely it is that you can traverse the object tree successfully without the optional chaining syntax, as there is more chance of nested objects being undefined.

Also, note that the optional chaining syntax works for returned results like `Object.keys(feed)`

In `AsteroidSearchPage.css`, which we should create in the `src` folder, we put:

```
.AsteroidsSearchPage{  
  margin: 0 auto;  
  width: 90vw;  
}
```

To add some margins to the page.

Next, we build the home page. Create a file called `HomePage.js` in the `src` folder and add:

```jsx
import React, { useState, useEffect } from "react";  
import { browse } from "./requests";  
import Card from "react-bootstrap/Card";  
import "./HomePage.css";

function HomePage() {  
  const [initialized, setIntialized] = useState(false);  
  const [feed, setFeed] = useState([]);
  const browserFeed = async () => {  
    const response = await browse();  
    setFeed(response.data.near_earth_objects);  
    setIntialized(true);  
  };

  useEffect(() => {  
    if (!initialized) {  
      browserFeed();  
    }  
  });  
  return (  
    <div className="HomePage">  
      <h1 className='center'>Asteroids Close to Earth</h1>  
      <br />  
      {feed?.map(f => {  
        return (  
          <Card style={{ width: "90vw", margin: "0 auto" }}>  
            <Card.Body>  
              <Card.Title>{f?.name}</Card.Title>  
              <div>  
                {f?.close_approach_data?.length > 0 ? (  
                  <div>  
                    Close Approach Date:  
                    {f?.close_approach_data?.map(d => {  
                      return <p>{d?.close_approach_date_full}</p>;  
                    })}  
                  </div>  
                ) : null}  
                <p>  
                  Minimum Estimated Diameter:{" "}  
                  {f?.estimated_diameter?.kilometers?.estimated_diameter_min} km  
                </p>  
                <p>  
                  Maximum Estimated Diameter:{" "}  
                  {f?.estimated_diameter?.kilometers?.estimated_diameter_max} km  
                </p>  
              </div>  
            </Card.Body>  
          </Card>  
        );  
      })}  
    </div>  
  );  
}

export default HomePage;
```

This page is very similar to the `AsteroidSearchPage` component with the use of the optional chaining syntax.

Next, create `HomePage.css` in the `src` folder and add:

```css
.HomePage{  
  text-align: left;  
}
```

To align our text to the left.

Next, we create `requests.js` in the `src` folder and add:

```javascript
const APIURL = "https://api.nasa.gov/neo/rest/v1]";  
const axios = require("axios");

export const searchFeed = data =>  
  axios.get(  
    `${APIURL}/feed?start_date=${data.startDate}&end_date=${data.endDate}&api_key=${process.env.REACT_APP_APIKEY}`  
  );

export const browse = () =>  
  axios.get(`${APIURL}/neo/browse?api_key=${process.env.REACT_APP_APIKEY}`);
```

To add the functions for making the HTTP requests to the NASA API for getting asteroid data and searching for them.

`process.env.REACT_APP_APIKEY` has the API key when you add the API key to the `.env` file of your project with `REACT_APP_APIKEY` as the key. Register for an [API key at NASA](https://api.nasa.gov/#apply-for-an-api-key).

Finally, we add `TopBar.js` to the `src` folder and add:

```jsx
import React from "react";  
import Navbar from "react-bootstrap/Navbar";  
import Nav from "react-bootstrap/Nav";  
import { withRouter } from "react-router-dom";function TopBar({ location }) {  
  const { pathname } = location;  
  return (  
    <Navbar bg="primary" expand="lg" variant="dark">  
      <Navbar.Brand href="#home">NASA App</Navbar.Brand>  
      <Navbar.Toggle aria-controls="basic-navbar-nav" />  
      <Navbar.Collapse id="basic-navbar-nav">  
        <Nav className="mr-auto">  
          <Nav.Link href="/" active={pathname == "/"}>  
            Home  
          </Nav.Link>  
          <Nav.Link href="/search" active={pathname.includes("/search")}>  
            Search  
          </Nav.Link>  
        </Nav>  
      </Navbar.Collapse>  
    </Navbar>  
  );  
}

export default withRouter(TopBar);
```

This is the navigation bar at the top of each page in our app. We set the `active` prop by checking the current URL of the page so we get highlights in our links.