Categories
TypeScript

How To Use TypeScript to Avoid Bugs

TypeScript is a superset of JavaScript that is created to address common problems that JavaScript has.

A problem that JavaScript has is that all objects have dynamic types. This means that you have no way of knowing which properties an objects has without logging it in a debugger.

This creates a lot of frustration as you have to check every object yourself, slowing down development. Without static types, you also cannot have auto-complete in your editor as there is no way to know what are in those objects with 100% certainty.

Static typing is optional so you can still use objects as hashes.

Type Checks

Also, you can put any argument into your JavaScript functions, which brings the same difficulties as the dynamic types as there is no enforcement of what is passed in.

This creates problems as arguments that you’re supposed to pass in but didn’t will be undefined and then you get undefined errors. There is also nothing stopping you from passing in too many arguments.

With both checks, TypeScript makes code easier to understand and follow. You don’t have to worry about breaking things when you change code as the compiler will tell you that you got those basic errors.

In addition, dependency injection is part of TypeScript, which means you don’t have to resolve dependencies yourself. It also makes mocking dependencies easy for testing.

TypeScript provides features in the upcoming versions of JavaScript (which is not finalized yet) that might be handy for some developers.

TypeScript adds types to your objects by defining type files for your objects. It works by setting up a module and then including a module.d.ts file with it.

An example of this is in the TypeScript docs.

// Type definitions for [~THE LIBRARY NAME~] [~OPTIONAL VERSION NUMBER~]
// Project: [~THE PROJECT NAME~]
// Definitions by: [~YOUR NAME~] <[~A URL FOR YOU~]>
/*~ This is the module template file. You should rename it to index.d.ts
 *~ and place it in a folder with the same name as the module.
 *~ For example, if you were writing a file for "super-greeter", this
 *~ file should be 'super-greeter/index.d.ts'
 */
/*~ If this module is a UMD module that exposes a global variable 'myLib' when
 *~ loaded outside a module loader environment, declare that global here.
 *~ Otherwise, delete this declaration.
 */
export as namespace myLib;

/*~ If this module exports functions, declare them like so.
 */
export function myFunction(a: string): string;
export function myOtherFunction(a: number): number;

/*~ You can declare types that are available via importing the module */
export interface SomeType {
  name: string;
  length: number;
  extras?: string[];
}
/*~ You can declare properties of the module using const, let, or var */
export const myField: number;

According to the example from the website, you can define top-level properties as a line of code. And, nested properties can be defined by using the keyword namespace.

Combining Types

You do not have to use single types in TypeScript. Multiple types can be combined into one. Intersection types are data types where multiple data types are combined into one.

For example:

function(foo: Foo, bar: Bar): Foo & Bar {
    const result: Partial<Foo & Bar> = {};
    for (const prop in foo) {
        if (foo.hasOwnProperty(prop)) {
            (result as Foo)[prop] = foo[prop];
        }
    }
    for (const prop in bar) {
        if (bar.hasOwnProperty(prop)) {
            (result as Bar)[prop] = bar[prop];
        }
    }
    return result as Foo& Bar;
}

The object with the combined type gets to properties from both types.

Union types are a data type where an object can either be one type or another. For example:

let foo: number|string;

After that declaration, you can assign a number or a string to the variable and the TypeScript compiler will not throw an error.

TypeScript allows for static typing while still allowing for flexibility.

With TypeScript, ES6 or later, these features are all available. let and const should be used to avoid scope collision that happens with var.

You also get new types of tuples and enums. You can define a tuple, an array with a fixed number of elements and type, by doing:

let x: [string, number] = ["hello", 1];


If you set x to [1, 'hello'], the TypeScript compiler will fail.

You can define an enum type for a list of constants. For example:

enum Color {Red, Green, Blue}
let green: Color = Color.Green;

Arrays can have fixed types in TypeScript. For example, to define an array of numbers, we put:

let x: number[] = [1,2,3];

Equivalently, you can assign an array like:

let x: Array<number> = [1, 2, 3];

If you set x to ['1','2','3'], compilation will fail.

TypeScript also adds an access modifier to properties and methods. You can optionally set properties and methods as public, private, or protected.

public properties and methods are available to all outside classes. protected properties and methods are available to subclasses and the current class. private properties and methods are available within the class only.

For example:

class classA {
  public publicProp: number;
  private privateProp: number;
}

class classB {
  public a: classA;
}

let b: classB = new classB();
b.a: classA = new classA();
b.a.publicProp = 1; // OK
b.a.privateProp = 1; // error

The last line will give an error since privateProp is not available to outside classes.

To include TypeScript in your project, you can run the following in your JavaScript project folder:

$ npm install typescript --save-dev

These are some basic and important advantages that TypeScript offers. With those features, it will save you a lot of time developing JavaScript apps!

Categories
TypeScript

Cool New Features Released in TypeScript 3.7

With the release of TypeScript 3.7, some great new features that are included from ES2020 that are now part of TypeScript. The new features include things like optional chaining, nullish coalescing, check for uncalled functions, and more.

In this article, we’ll look at some of them in more detail.

Optional Chaining

Optional chaining is a feature that lets us get a deeply nested property of an object without worrying if any of the properties are null or undefined.

We can use the ?. operator to get the properties of an object instead of using the usual dot operator. For example, instead of writing:

let x = a.b.c.d;

We write:

let x = a?.b?.c?.d;

With the optional chaining operator, we don’t have to worry about properties b or c being null or undefined, which saves us from writing lots of code to check is these properties exist.

If any of the intermediate properties are null or undefined, then undefined is returned instead of crashing the app with errors.

This means that we no longer have to write something like:

let x = a && a.b && a.b.c && a.b.c.d;

To get the d property from the a object.

One thing to be careful is that if strictNullChecks flag is on, then we’ll get errors if we operate on an expression with the optional chaining operator inside a function with a parameter that has optional parameter as the operand.

For example, if we write:

let a = { b: { c: { d: 100 } } };
const divide = (a?: { b: { c: { d: 100 } } }) => {
  return a?.b?.c?.d / 100;
}

Then we get the error ‘Object is possibly ‘undefined’.(2532)’.

Nullish Coalescing

The nullish coalescing operator lets us assign a default value to something when something is null or undefined.

An expression with the nullish coalescing looks like:

let x = foo ?? bar;

Where the ?? is the nullish coalescing operator.

For example, we can use it as follows:

let x = null;
let y = x ?? 0;
console.log(y); // 0

Then we get 0 as the value of y .

This is handy because the alternative was to use the || operator for assigning default values. Any falsy value on the left operand would cause the default value on the right operand to be assigned, which we might not always want.

The nullish coalescing operator only assigns the default value when the left operand is null or undefined.

For example:

let x = 0;
let y = x || 0.5;

In the code above, 0 is a valid value that can be assigned to y, but we still assign the default value of 0.5 to it because 0 is falsy, which we don’t want.

Assertion Functions

TypeScript 3.7 comes with the asserts keyword which let us write our own functions to run some checks on our data and throws an error if the check fails.

For example, we can write the following to check if a parameter passed into our assertion function is a number:

function assertIsNumber(x: any): asserts x is number {
    if (typeof x === 'number') {
        throw new Error('x is not a number');
    }
}
assertIsNumber('1');
assertIsNumber(1);

When we run the code above, we should get ‘Uncaught Error: x is not a number’.

In the code above, the asserts keyword checks whatever condition comes after it.

It’s a function that returns void which means that it doesn’t return anything. It can only throw errors if it doesn’t meet the condition we define.

Better Support for Functions that Return the Never Type

With TypeScript 3.7, now the TypeScript compiler recognizes functions that returns the never type is run in a function that returns some other type.

For example, before TypeScript 3.7, we have to write the following to avoid an error:

const neverFn = (): never => {
    throw new Error();
};

const foo = (x: string | number): number => {
    if (typeof x === 'string') {
        return +x;
    }
    else if (typeof x === 'number') {
        return x;
    }
    return neverFn();
}

The code above would get us the error “Function lacks ending return statement and return type does not include ‘undefined’.” Because we did add return before the neverFn() function call.

TypeScript versions earlier than 3.7 don’t recognize the never function as a valid path because it doesn’t allow a path in the code that returns undefined if a return type is specified.

Now removing the return in return neverFn(); will work if TypeScript 3.7 is used to compile the code.

Allowing Some Recursive Type Aliases

Type aliases that aren’t assigned to itself are now allowed with TypeScript 3.7. For example, the following is still not allowed:

type Bar = Bar;

since it just replaces the Bar type with itself forever.

If we try to compile the code above, we would get the error “Type alias ‘Bar’ circularly references itself. (2456)“.

However, now we can write something like:

interface Foo { };
interface Baz { };
type Bar = Foo | Baz | Bar[];

This is because the Bar[] type isn’t directly replacing Bar , so this type of recursion is allowed.

Generating Declaration Files when AllowJs Flag is On

Before TypeScript 3.7, we can’t generate declaration files when the --allowJs flag is on, so TypeScript code that is compiled with JavaScript can’t have any declaration files generated.

This means that type checking can’t be done with JavaScript files that are being compiled, even if they have JSDoc declarations.

With this change, now type checking can be done with those JavaScript files.

Now we can write libraries with JSDoc annotated JavaScript and support TypeScript users with the same code.

The TypeScript compiler since 3.7 will infer the types of the JavaScript code from the JSDoc comments.

Uncalled Function Checks

Forgetting to call a function by omitting the parentheses is a problem that sometimes causes bugs. For example, if we write the following:

const foo = () => { };

const bar = () => {
    if (foo) {
        return true;
    }
}

We’ll get the error “This condition will always return true since the function is always defined. Did you mean to call it instead?(2774)” when we write the code above and try to compile it with the TypeScript 3.7 compiler.

This code wouldn’t give any errors before version 3.7.

Conclusion

As we can see, TypeScript 3.7 gives us a lot of useful new features that aren’t available before. Optional chaining and nullish coaslecing operators are handy for avoiding null or undefined errors.

Recognizing function calls that return the never type is also handy for writing code with paths that don’t always return.

Having recursive type aliases help with combining some kinds of types into one alias.

For developers that write libraries, TypeScript 3.7 can infer types from JavaScript files that are compiled with the TypeScript compiler by checking the JSDoc comments.

Finally, uncalled functions are now checked by the TypeScript compiler if they’re written like we’re trying to access it as a property.

Categories
React TypeScript

How to Build React Apps with TypeScript with a Practical Example

Overview

TypeScript is a superset of JavaScript that was created to address many common problems — the biggest being that JavaScript variables and objects have all dynamic types. This means that you have no way to know what properties an object has without logging it in a debugger.

This creates a lot of frustration as you have to check each individual object yourself, slowing down development. Without static types, you also cannot have auto-complete in your editor since there is no way to know what are in those objects are with 100% certainty.

Also, you can put any argument into your JavaScript functions, so there is no enforcement to what is passed in. This creates problems when you pass the wrong argument types expected or don’t pass in enough arguments, making those parameters undefined. There is also nothing stopping you from passing in too many arguments.

By being able to detect all these issues at compile-time, TypeScript makes code easier to understand and follow while decreasing the number of bugs sent to production. You don’t have to worry about breaking things when you change code as the compiler will tell you that you got those basic errors.

Create React App projects can be started with TypeScript using the --typscript option. However, we need to install type definitions for some libraries ourselves.

In this article, we will build an address book app using React and TypeScript.

Getting Started

To start, we need to run Create React App to scaffold the app. We run npx create-react-app address-book --typscript to create the app project folder with the initial files. The app will have a home page to display the contacts and let us open a modal to add a contact. There will be a table that displays all the contacts, and Edit and Delete buttons on each row.

The contacts will be stored in a central MobX store, making them easy to access. React Router will be used for routing. Contacts will be saved in the back end by using the JSON server. We will also use Formik and Yup for form management and validation.

To install the libraries we mentioned above, we run npm i axios bootstrap formik react-bootstrap mobx mobx-react react-router-dom yup. Axios is the HTTP client that we use for making HTTP requests to back end. react-router-dom is the package name for the latest version of React Router.

We also need to install type definitions for our libraries by running:

npm i --save-dev @types/react @types/react-router-dom @types/react-bootstrap @types/react-dom @types/yup

In tsconfig.json, we replace the existing code with:

{
  "compilerOptions": {
    "target": "es5",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react",
    "noImplicitThis": false,
    "experimentalDecorators": true
  },
  "include": [
    "src"
  ]
}

This disables noImplicitThis so we can use bind(this) in our components and add experimentalDecorators to use decorators needed by MobX.

Building the React App

We create some custom types for the code we will write. Create an interfaces.ts file in the src folder and add:

import { ContactsStore } from './store';

export interface Contact {
    firstName: string;
    lastName: string;
    address: string;
    city: string;
    region: string;
    postalCode: string;
    phone: string;
    email: string;
    age: string;
    id: number;
    country: string;
}

export interface HomePageProps {
    contactsStore: ContactsStore
}

export interface ContactFormProps {
    edit?: boolean,
    onSave?: any,
    contact?: Contact,
    onCancelAdd?: any,
    onCancelEdit?: any,
    contactsStore: ContactsStore
}

This will be the types for our data and props.

Now that we have all the libraries installed, we can start building the app. All files will be in the src folder unless mentioned otherwise. First we work on the MobX store. We create a file called store.tsx in the src folder and add the following:

import { observable } from "mobx";
import { Contact } from "./interfaces";

class ContactsStore {
    [@observable](http://twitter.com/observable "Twitter profile for @observable") contacts: Contact[] = [];

    setContacts(contacts: Contact[]) {
        this.contacts = contacts;
    }
}

export { ContactsStore };

This is a simple store that holds the contacts. The contacts array is where we store the contacts for the whole app. The setContacts function lets us set contacts from any component where we pass in this store object to.

This block:

ContactsStore = decorate(ContactsStore, {
  contacts: observable,
  setContacts: action,
});

designates the contacts array in ContactsStore as the entity that can be watched by components for changes. The setContacts is the function that can be used to set the contacts array in the store.

In App.tsx, we replace the existing code with the following:

import React from "react";
import { Router, Route } from "react-router-dom";
import HomePage from "./HomePage";
import { createBrowserHistory as createHistory } from "history";
import Navbar from "react-bootstrap/Navbar";
import Nav from "react-bootstrap/Nav";
import "./App.css";
const history = createHistory();

function App({ contactsStore }: any) {
  return (
    <div className="App">
      <Router history={history}>
        <Navbar bg="primary" expand="lg" variant="dark">
          <Navbar.Brand href="#home">Address Book App</Navbar.Brand>
          <Navbar.Toggle aria-controls="basic-navbar-nav" />
          <Navbar.Collapse id="basic-navbar-nav">
            <Nav className="mr-auto">
              <Nav.Link href="/">Home</Nav.Link>
            </Nav>
          </Navbar.Collapse>
        </Navbar>
        <Route
          path="/"
          exact
          component={(props: any) => (
            <HomePage {...props} contactsStore={contactsStore} />
          )}
        />
      </Router>
    </div>
  );
}

export default App;

We pass the store into any component that needs it, like the HomePage. It will then pass the component to the ContactForm.

This is where we add the navigation bar and show our routes handled by React Router. In App.css, we replace the existing code with:

.App {
  text-align: center;
}

Next we build our contact form. This is the most logic heavy part of our app.

In this ContactForm.tsx, we have the React.SFC<ContactFormProps> annotation for the ContactForm component so we get type checking for our component. The ContactFormProps lets us check the data type of our props.

Create a file called ContactForm.tsx and add:

import React from "react";
import { Formik } from "formik";
import Form from "react-bootstrap/Form";
import Col from "react-bootstrap/Col";
import InputGroup from "react-bootstrap/InputGroup";
import Button from "react-bootstrap/Button";
import * as yup from "yup";
import { COUNTRIES } from "./exports";
import { addContact, editContact, getContacts } from "./requests";
import { FormControl } from "react-bootstrap";
import { Contact, ContactFormProps } from "./interfaces";

const schema = yup.object({
  firstName: yup.string().required("First name is required"),
  lastName: yup.string().required("Last name is required"),
  address: yup.string().required("Address is required"),
  city: yup.string().required("City is required"),
  region: yup.string().required("Region is required"),
  country: yup
    .string()
    .required("Country is required")
    .default("Afghanistan"),
  postalCode: yup
    .string()
    .when("country", {
      is: "United States",
      then: yup
        .string()
        .matches(/^[0-9]{5}(?:-[0-9]{4})?$/, "Invalid postal code")
    })
    .when("country", {
      is: "Canada",
      then: yup
        .string()
        .matches(/^[A-Za-z]d[A-Za-z][ -]?d[A-Za-z]d$/, "Invalid postal code")
    })
    .required(),
  phone: yup
    .string()
    .when("country", {
      is: country => ["United States", "Canada"].includes(country),
      then: yup
        .string()
        .matches(/^[2-9]d{2}[2-9]d{2}d{4}$/, "Invalid phone nunber")
    })
    .required(),
  email: yup
    .string()
    .email("Invalid email")
    .required("Email is required"),
  age: yup
    .number()
    .required("Age is required")
    .min(0, "Minimum age is 0")
    .max(200, "Maximum age is 200")
});

const ContactForm: React.SFC<ContactFormProps> = ({
  edit,
  onSave,
  contact,
  onCancelAdd,
  onCancelEdit,
  contactsStore
}: ContactFormProps) => {
  const handleSubmit = async (evt: Contact) => {
    const isValid = await schema.validate(evt);
    if (!isValid) {
      return;
    }
    if (!edit) {
      await addContact(evt);
    } else {
      await editContact(evt);
    }
    const response = await getContacts();
    contactsStore.setContacts(response.data);
    onSave();
  };

  return (
    <>
      <Formik
        validationSchema={schema}
        onSubmit={handleSubmit}
        initialValues={(contact || {}) as any}
      >
        {({
          handleSubmit,
          handleChange,
          handleBlur,
          values,
          touched,
          isInvalid,
          errors
        }: any) => (
          <Form noValidate onSubmit={handleSubmit}>
            <Form.Row>
              <Form.Group as={Col} md="12" controlId="firstName">
                <Form.Label>First name</Form.Label>
                <Form.Control
                  type="text"
                  name="firstName"
                  placeholder="First Name"
                  value={values.firstName || ""}
                  onChange={handleChange}
                  isInvalid={touched.firstName && errors.firstName}
                />
                <Form.Control.Feedback type="invalid">
                  {errors.firstName}
                </Form.Control.Feedback>
              </Form.Group>
              <Form.Group as={Col} md="12" controlId="lastName">
                <Form.Label>Last name</Form.Label>
                <Form.Control
                  type="text"
                  name="lastName"
                  placeholder="Last Name"
                  value={values.lastName || ""}
                  onChange={handleChange}
                  isInvalid={touched.firstName && errors.lastName}
                />

                <Form.Control.Feedback type="invalid">
                  {errors.lastName}
                </Form.Control.Feedback>
              </Form.Group>
              <Form.Group as={Col} md="12" controlId="address">
                <Form.Label>Address</Form.Label>
                <InputGroup>
                  <Form.Control
                    type="text"
                    placeholder="Address"
                    aria-describedby="inputGroupPrepend"
                    name="address"
                    value={values.address || ""}
                    onChange={handleChange}
                    isInvalid={touched.address && errors.address}
                  />
                  <Form.Control.Feedback type="invalid">
                    {errors.address}
                  </Form.Control.Feedback>
                </InputGroup>
              </Form.Group>
            </Form.Row>
            <Form.Row>
              <Form.Group as={Col} md="12" controlId="city">
                <Form.Label>City</Form.Label>
                <Form.Control
                  type="text"
                  placeholder="City"
                  name="city"
                  value={values.city || ""}
                  onChange={handleChange}
                  isInvalid={touched.city && errors.city}
                />

                <Form.Control.Feedback type="invalid">
                  {errors.city}
                </Form.Control.Feedback>
              </Form.Group>
              <Form.Group as={Col} md="12" controlId="region">
                <Form.Label>Region</Form.Label>
                <Form.Control
                  type="text"
                  placeholder="Region"
                  name="region"
                  value={values.region || ""}
                  onChange={handleChange}
                  isInvalid={touched.region && errors.region}
                />
                <Form.Control.Feedback type="invalid">
                  {errors.region}
                </Form.Control.Feedback>
              </Form.Group>

              <Form.Group as={Col} md="12" controlId="country">
                <Form.Label>Country</Form.Label>
                <FormControl
                  as="select"
                  placeholder="Country"
                  name="country"
                  onChange={handleChange}
                  value={values.country || ""}
                  isInvalid={touched.region && errors.country}
                >
                  {COUNTRIES.map(c => (
                    <option key={c} value={c}>
                      {c}
                    </option>
                  ))}
                </FormControl>
                <Form.Control.Feedback type="invalid">
                  {errors.country}
                </Form.Control.Feedback>
              </Form.Group>

              <Form.Group as={Col} md="12" controlId="postalCode">
                <Form.Label>Postal Code</Form.Label>
                <Form.Control
                  type="text"
                  placeholder="Postal Code"
                  name="postalCode"
                  value={values.postalCode || ""}
                  onChange={handleChange}
                  isInvalid={touched.postalCode && errors.postalCode}
                />

                <Form.Control.Feedback type="invalid">
                  {errors.postalCode}
                </Form.Control.Feedback>
              </Form.Group>

              <Form.Group as={Col} md="12" controlId="phone">
                <Form.Label>Phone</Form.Label>
                <Form.Control
                  type="text"
                  placeholder="Phone"
                  name="phone"
                  value={values.phone || ""}
                  onChange={handleChange}
                  isInvalid={touched.phone && errors.phone}
                />

                <Form.Control.Feedback type="invalid">
                  {errors.phone}
                </Form.Control.Feedback>
              </Form.Group>

              <Form.Group as={Col} md="12" controlId="email">
                <Form.Label>Email</Form.Label>
                <Form.Control
                  type="text"
                  placeholder="Email"
                  name="email"
                  value={values.email || ""}
                  onChange={handleChange}
                  isInvalid={touched.email && errors.email}
                />

                <Form.Control.Feedback type="invalid">
                  {errors.email}
                </Form.Control.Feedback>
              </Form.Group>

              <Form.Group as={Col} md="12" controlId="age">
                <Form.Label>Age</Form.Label>
                <Form.Control
                  type="text"
                  placeholder="Age"
                  name="age"
                  value={values.age || ""}
                  onChange={handleChange}
                  isInvalid={touched.age && errors.age}
                />

                <Form.Control.Feedback type="invalid">
                  {errors.age}
                </Form.Control.Feedback>
              </Form.Group>
            </Form.Row>
            <Button type="submit" style={{ marginRight: "10px" }}>
              Save
            </Button>
            {edit ? (
              <Button type="button" onClick={onCancelEdit}>
                Cancel
              </Button>
            ) : (
              <Button type="button" onClick={onCancelAdd}>
                Cancel
              </Button>
            )}
          </Form>
        )}
      </Formik>
    </>
  );
};

export default ContactForm;

We pass in the contactsStore from the HomePage component, allowing us to use the data and functions in contactsStore.

For form validation, we use Formik to facilitate building our contact form here, with our Boostrap Form component nested in the Formik component so that we can use Formik’s handleChange, handleSubmit, values, touched and errors parameters.

handleChange is a function that lets us update the form field data from the inputs without writing the code ourselves. handleSubmit is the function that we passed into the onSubmit handler of the Formik component. The parameter in the function is the data we entered, with the field name as the key, as defined by the name attribute of each field and the value of each field as the value of those keys. Notice that in each value prop, we have ||'' so we do not get undefined values and prevent uncontrolled form warnings from getting triggered.

To display form validation messages, we have to pass in the isInvalid prop to each Form.Control component. The schema object is what Formik will check against for form validation. The argument in the required function is the validation error message. The second argument of the matches, min, and max functions are also validation messages.

The parameter of the ContactForm function are props, which we will pass in from the HomePage component that we will build later. The handleSubmit function checks if the data is valid, then if it is then it will proceed to saving according to whether it is adding or editing a contact. Then when saving is successful we set the contacts in the store and call onSave prop, which is a function to close the modal the form is in. The modal will be defined in the home page.

Next we create a file called exports.ts, and put:

export const COUNTRIES = [
  "Afghanistan",
  "Albania",
  "Algeria",
  "Andorra",
  "Angola",
  "Anguilla",
  "Antigua &amp; Barbuda",
  "Argentina",
  "Armenia",
  "Aruba",
  "Australia",
  "Austria",
  "Azerbaijan",
  "Bahamas",
  "Bahrain",
  "Bangladesh",
  "Barbados",
  "Belarus",
  "Belgium",
  "Belize",
  "Benin",
  "Bermuda",
  "Bhutan",
  "Bolivia",
  "Bosnia &amp; Herzegovina",
  "Botswana",
  "Brazil",
  "British Virgin Islands",
  "Brunei",
  "Bulgaria",
  "Burkina Faso",
  "Burundi",
  "Cambodia",
  "Cameroon",
  "Canada",
  "Cape Verde",
  "Cayman Islands",
  "Chad",
  "Chile",
  "China",
  "Colombia",
  "Congo",
  "Cook Islands",
  "Costa Rica",
  "Cote D Ivoire",
  "Croatia",
  "Cruise Ship",
  "Cuba",
  "Cyprus",
  "Czech Republic",
  "Denmark",
  "Djibouti",
  "Dominica",
  "Dominican Republic",
  "Ecuador",
  "Egypt",
  "El Salvador",
  "Equatorial Guinea",
  "Estonia",
  "Ethiopia",
  "Falkland Islands",
  "Faroe Islands",
  "Fiji",
  "Finland",
  "France",
  "French Polynesia",
  "French West Indies",
  "Gabon",
  "Gambia",
  "Georgia",
  "Germany",
  "Ghana",
  "Gibraltar",
  "Greece",
  "Greenland",
  "Grenada",
  "Guam",
  "Guatemala",
  "Guernsey",
  "Guinea",
  "Guinea Bissau",
  "Guyana",
  "Haiti",
  "Honduras",
  "Hong Kong",
  "Hungary",
  "Iceland",
  "India",
  "Indonesia",
  "Iran",
  "Iraq",
  "Ireland",
  "Isle of Man",
  "Israel",
  "Italy",
  "Jamaica",
  "Japan",
  "Jersey",
  "Jordan",
  "Kazakhstan",
  "Kenya",
  "Kuwait",
  "Kyrgyz Republic",
  "Laos",
  "Latvia",
  "Lebanon",
  "Lesotho",
  "Liberia",
  "Libya",
  "Liechtenstein",
  "Lithuania",
  "Luxembourg",
  "Macau",
  "Macedonia",
  "Madagascar",
  "Malawi",
  "Malaysia",
  "Maldives",
  "Mali",
  "Malta",
  "Mauritania",
  "Mauritius",
  "Mexico",
  "Moldova",
  "Monaco",
  "Mongolia",
  "Montenegro",
  "Montserrat",
  "Morocco",
  "Mozambique",
  "Namibia",
  "Nepal",
  "Netherlands",
  "Netherlands Antilles",
  "New Caledonia",
  "New Zealand",
  "Nicaragua",
  "Niger",
  "Nigeria",
  "Norway",
  "Oman",
  "Pakistan",
  "Palestine",
  "Panama",
  "Papua New Guinea",
  "Paraguay",
  "Peru",
  "Philippines",
  "Poland",
  "Portugal",
  "Puerto Rico",
  "Qatar",
  "Reunion",
  "Romania",
  "Russia",
  "Rwanda",
  "Saint Pierre &amp; Miquelon",
  "Samoa",
  "San Marino",
  "Satellite",
  "Saudi Arabia",
  "Senegal",
  "Serbia",
  "Seychelles",
  "Sierra Leone",
  "Singapore",
  "Slovakia",
  "Slovenia",
  "South Africa",
  "South Korea",
  "Spain",
  "Sri Lanka",
  "St Kitts &amp; Nevis",
  "St Lucia",
  "St Vincent",
  "St. Lucia",
  "Sudan",
  "Suriname",
  "Swaziland",
  "Sweden",
  "Switzerland",
  "Syria",
  "Taiwan",
  "Tajikistan",
  "Tanzania",
  "Thailand",
  "Timor L'Este",
  "Togo",
  "Tonga",
  "Trinidad &amp; Tobago",
  "Tunisia",
  "Turkey",
  "Turkmenistan",
  "Turks &amp; Caicos",
  "Uganda",
  "Ukraine",
  "United Arab Emirates",
  "United Kingdom",
  "United States",
  "United States Minor Outlying Islands",
  "Uruguay",
  "Uzbekistan",
  "Venezuela",
  "Vietnam",
  "Virgin Islands (US)",
  "Yemen",
  "Zambia",
  "Zimbabwe",
];

These are countries for the countries field in the form.

In index.tsx, replace the existing code with:

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import * as serviceWorker from "./serviceWorker";
import { ContactsStore } from "./store";
const contactsStore = new ContactsStore();

ReactDOM.render(
  <App contactsStore={contactsStore} />,
  document.getElementById("root")
);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

This passes our MobX store into our App component.

In HomePage.tsx, we put:

import React from "react";
import { useState, useEffect } from "react";
import Table from "react-bootstrap/Table";
import ButtonToolbar from "react-bootstrap/ButtonToolbar";
import Button from "react-bootstrap/Button";
import Modal from "react-bootstrap/Modal";
import ContactForm from "./ContactForm";
import "./HomePage.css";
import { getContacts, deleteContact } from "./requests";
import { observer } from "mobx-react";
import { Contact, HomePageProps } from "./interfaces";

const HomePage: React.SFC<HomePageProps> = ({
  contactsStore
}: HomePageProps) => {
  const [openAddModal, setOpenAddModal] = useState(false);
  const [openEditModal, setOpenEditModal] = useState(false);
  const [initialized, setInitialized] = useState(false);
  const [selectedContact, setSelectedContact] = useState({});

  const openModal = () => {
    setOpenAddModal(true);
  };

  const closeModal = () => {
    setOpenAddModal(false);
    setOpenEditModal(false);
    getData();
  };

  const cancelAddModal = () => {
    setOpenAddModal(false);
  };

  const editContact = (contact: Contact) => {
    setSelectedContact(contact);
    setOpenEditModal(true);
  };

  const cancelEditModal = () => {
    setOpenEditModal(false);
  };

  const getData = async () => {
    const response = await getContacts();
    contactsStore.setContacts(response.data);
    setInitialized(true);
  };

  const deleteSelectedContact = async (id: number) => {
    await deleteContact(id);
    getData();
  };

  useEffect(() => {
    if (!initialized) {
      getData();
    }
  });

  return (
    <div className="home-page">
      <h1>Contacts</h1>
      <Modal show={openAddModal} onHide={closeModal}>
        <Modal.Header closeButton>
          <Modal.Title>Add Contact</Modal.Title>
        </Modal.Header>
        <Modal.Body>
          <ContactForm
            edit={false}
            onSave={closeModal.bind(this) as any}
            onCancelAdd={cancelAddModal as any}
            contactsStore={contactsStore}
          />
        </Modal.Body>
      </Modal>

      <Modal show={openEditModal} onHide={closeModal}>
        <Modal.Header closeButton>
          <Modal.Title>Edit Contact</Modal.Title>
        </Modal.Header>
        <Modal.Body>
          <ContactForm
            edit={true}
            onSave={closeModal.bind(this)}
            contact={selectedContact as Contact}
            onCancelEdit={cancelEditModal}
            contactsStore={contactsStore}
          />
        </Modal.Body>
      </Modal>
      <ButtonToolbar onClick={openModal}>
        <Button variant="outline-primary">Add Contact</Button>
      </ButtonToolbar>
      <br />
      <Table striped bordered hover>
        <thead>
          <tr>
            <th>First Name</th>
            <th>Last Name</th>
            <th>Address</th>
            <th>City</th>
            <th>Country</th>
            <th>Postal Code</th>
            <th>Phone</th>
            <th>Email</th>
            <th>Age</th>
            <th>Edit</th>
            <th>Delete</th>
          </tr>
        </thead>
        <tbody>
          {contactsStore.contacts.map((c: any) => (
            <tr key={c.id}>
              <td>{c.firstName}</td>
              <td>{c.lastName}</td>
              <td>{c.address}</td>
              <td>{c.city}</td>
              <td>{c.country}</td>
              <td>{c.postalCode}</td>
              <td>{c.phone}</td>
              <td>{c.email}</td>
              <td>{c.age}</td>
              <td>
                <Button
                  variant="outline-primary"
                  onClick={editContact.bind(this, c)}
                >
                  Edit
                </Button>
              </td>
              <td>
                <Button
                  variant="outline-primary"
                  onClick={deleteSelectedContact.bind(this, c.id)}
                >
                  Delete
                </Button>
              </td>
            </tr>
          ))}
        </tbody>
      </Table>
    </div>
  );
};
export default observer(HomePage);

Notice that we have export default observer(HomePage); instead of export default HomePage;. We need to wrap HomePage with the observer function call so that the latest data from the store will be propagated into this component.

We have the React.SFC<HomePageProps> annotation for the HomePage component so we get type checking for our component. The HomePageProps lets us check the data type of our props.

It has the table for displaying the contacts and buttons to add, edit, and delete a contact. It gets data once on the first load with the getData function called in the useEffect’s callback function. useEffect’s callback is called on every render so we want to set a initialized flag and check that it loads only if it’s true.

Note that we pass in all the props of this component to the ContactForm component. To pass an argument for the onClick handler function, we have to call bind on the function and pass in the argument for the function as a second argument to bind. For example, in this file, we have editContact.bind(this, c), where c is the contact object. The editContact function is defined as follows:

const editContact = (contact) => {
    setSelectedContact(contact);
    setOpenEditModal(true);
  }

c is the contact parameter we pass in.

Next we create a file called HomePage.css and put:

.home-page {
  padding: 20px;
}

In index.tsx, we replace the existing code with:

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import * as serviceWorker from "./serviceWorker";
import { ContactsStore } from "./store";
const contactsStore = new ContactsStore();

ReactDOM.render(
  <App contactsStore={contactsStore} />,
  document.getElementById("root")
);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

Then we make a file called requests.tsx and add:

import { Contact } from "./interfaces";

const APIURL = 'http://localhost:3000';
const axios = require('axios');

export const getContacts = () => axios.get(`${APIURL}/contacts`);

export const addContact = (data: Contact) => axios.post(`${APIURL}/contacts`, data);

export const editContact = (data: Contact) => axios.put(`${APIURL}/contacts/${data.id}`, data);

export const deleteContact = (id: number) => axios.delete(`${APIURL}/contacts/${id}`);

These are functions are making our HTTP requests to the back end to save and delete contacts.

Finally, in public/index.html, we replace the existing code with:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8" />
  <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <meta name="theme-color" content="#000000" />
  <meta name="description" content="Web site created using create-react-app" />
  <link rel="apple-touch-icon" href="logo192.png" />
  <link rel="manifest" crossorigin="use-credentials" href="%PUBLIC_URL%/manifest.json" />

  <!--
      manifest.json provides metadata used when your web app is installed on a
      user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
    -->
  <!--
      Notice the use of %PUBLIC_URL% in the tags above.
      It will be replaced with the URL of the `public` folder during the build.
      Only files inside the `public` folder can be referenced from the HTML.

      Unlike  "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
      work correctly both with client-side routing and a non-root public URL.
      Learn how to configure a non-root public URL by running `npm run build`.
    -->
  <title>React Address Book App</title>
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
    integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous" />
</head>

<body>
  <noscript>You need to enable JavaScript to run this app.</noscript>
  <div id="root"></div>
  <!--
      This HTML file is a template.
      If you open it directly in the browser, you will see an empty page.

You can add webfonts, meta tags, or analytics to this file.
      The build step will place the bundled scripts into the <body> tag.

To begin the development, run `npm start` or `yarn start`.
      To create a production bundle, use `npm run build` or `yarn build`.
    -->
</body>

</html>

This changes the title and add the Bootstrap stylesheet.

Now we can run the app by running set PORT=3001 && react-scripts start on Windows or PORT=3006 react-scripts start on Linux.

To start back end, we first install the json-server package by running npm i json-server. Them go to our project folder and run:

json-server --watch db.json

In db.json, change the text to:

{
  "contacts": [
  ]
}

Now we have the contacts endpoints defined in requests.js available.

Categories
TypeScript

Introduction to TypeScript Interfaces

The big advantage of TypeScript over plain JavaScript is that it extends the features of JavaScript by adding type safety to our program’s objects. It does this by checking the shape of the values that objects can take on. Checking the shape is called duck typing or structural typing. It’s very useful for defining contracts within our code in TypeScript programs. In this article, we’ll look at how to define a TypeScript interface and add required or optional properties to it.

Defining Interfaces

To define a basic interface, we use the interface keyword in TypeScript. This keyword is exclusive to TypeScript and it’s not available in JavaScript. We can define a TypeScript interface like in the code below:

interface Person {
  name: string
}

In the code above, if a variable or parameter has been designated with this interface, then all objects with the type using it will have the name property. Object literals assigned to a variable with the type cannot have any other property. Arguments that are passed in as parameters that are of this type also can only have this property. For example, the following code will be compiled successfully and run with the TypeScript compiler:

interface Person{
  name: string
}

const greet = (person: Person) => {
  console.log(`Hello, ${person.name}`);
}
greet({ name: 'Joe' });

The code above will compile and run since it only has the name property and the value of it is a string exactly like it’s specified in the Person interface. However, the following code wouldn’t be compiled by the TypeScript compiler and throw an error:

interface Person {
  name: string
}

const greet = (person: Person) => {
  console.log(`Hello, ${person.name}`);
}

greet({ name: 'Joe', foo: 'abc' });

This is because we specified that the type of the person parameter in the greet function has the type Person. The object passed in as the argument can only have the name property and that the value of it can only be a string. We can’t assign an object literal with different properties than what’s listed in the interface if a variable has been designated by the type of the interface:

const p: Person = { name: 'Joe', foo: 'abc' };

The code above also wouldn’t compile since the object literal has an extra property in it that is not listed in the Person interface. In editors that support TypeScript like Visual Studio Code, we would get the error message “Type ‘{ name: string; foo: string; }’ is not assignable to type ‘Person’. Object literal may only specify known properties, and ‘foo’ does not exist in type ‘Person’.”

With interfaces, when designating the types of variables, we get auto-completion of our code when we write object literals.

Optional Properties

TypeScript interfaces can have optional properties. This makes interfaces much more flexible than just adding required properties to them. We can designate a property as optional with the question mark ? after the property name. For example, we can write the following code to define an interface with an optional property:

interface Person {
  name: string,
  age?: number
}

In the code above, age is an optional property since we put a question mark after the property name. We can use it as the following code:

interface Person {
  name: string,
  age?: number
}

const greet = (person: Person) => {
  console.log(`Hello, ${person.name}. ${person.age ? `You're ${person.age} years old.` : ''}`);
}

greet({ name: 'Joe', age: 10 });

In the object we passed into the greet function into the code above, we passed in the age property and a number for its value. Then we used it inside our code by checking if it’s defined first and then we add additional text to the main string if it is. We can also omit optional properties like in the following code:

interface Person {
  name: string,
  age?: number
}

const greet = (person: Person) => {
  console.log(`Hello, ${person.name}. ${person.age ? `You're ${person.age} years old.` : ''}`);
}

greet({ name: 'Joe' });

The code above would still run since the age property has been designated as being optional. The rules outlined by the interface still apply if we assign an object literal to it. For example, if we write:

interface Person {
  name: string,
  age?: number
}

const greet = (person: Person) => {
  console.log(`Hello, ${person.name}. ${person.age ? `You're ${person.age} years old.` : ''}`);
}

const person: Person = { name: 'Joe' };
greet(person);

This would still work since we stick to the property descriptions defined in the Person interface. If we add the age property with a number property it would still compile and run:

interface Person {
  name: string,
  age?: number
}

const greet = (person: Person) => {
  console.log(`Hello, ${person.name}. ${person.age ? `You're ${person.age} years old.` : ''}`);
}

const person: Person = { name: 'Joe', age: 20 }
greet(person);

Optional properties are useful in that we can define properties that can possibly be used while preventing the use of properties that aren’t part of the interface. This prevents bugs that arise because of typos in our code.

Readonly Properties

To make properties that are non-modifiable after the object is first created, we can use the readonly keyword in front of a property to designate that the property can only be written once when the object is being created and not any time after. For example, we can write the following code:

interface Person {
  readonly name: string,
  age?: number
}

const greet = (person: Person) => {
  console.log(`Hello, ${person.name}. ${person.age ? `You're ${person.age} years old.` : ''}`);
}

const person: Person = { name: 'Joe', age: 20 };
greet(person);

We added the readonly keyword in front of the name property so that it can only be changed once and only once. So if we try to assign something new to the name property, the TypeScript compiler wouldn’t compile and code and the code wouldn’t run:

interface Person{
  readonly name: string,
  age?: number
}

const greet = (person: Person) => {
  console.log(`Hello, ${person.name}. ${person.age ? `You're ${person.age} years old.` : ''}`);
}

let person: Person = { name: 'Joe', age: 20 };
person.name = 'Jane';
greet(person);

The code above will get us the error message “ Cannot assign to ‘name’ because it is a read-only property” when we try to compile it or in text editors that support TypeScript. However, we can reassign the whole object to a different value like in the following code:

interface Person {
  readonly name: string,
  age?: number
}

const greet = (person: Person) => {
  console.log(`Hello, ${person.name}. ${person.age ? `You're ${person.age} years old.` : ''}`);
}

let person: Person = { name: 'Joe', age: 20 };
person = { name: 'Jane', age: 20 };
greet(person);

Then instead of logging ‘Hello, Joe. You’re 20 years old.’, we get ‘Hello, Jane. You’re 20 years old.’

If we want to designate an array as read-only, that is, only be changed when it’s created, we can use the ReadonlyArray type that’s the same as Array, but all the mutating methods are removed from it so that we can’t accidentally change anything in the array. For example, we can use it as in the following code:

let readOnlyArray: ReadonlyArray<number> = [1,2,3];

Then if we try to write the following code, the TypeScript compiler would give errors:

readOnlyArray[0] = 12;
readOnlyArray.push(5);
readOnlyArray.length = 100;

Then we get the following errors:

Index signature in type 'readonly number[]' only permits reading.(2542)

Property 'push' does not exist on type 'readonly number[]'.(2339)

Cannot assign to 'length' because it is a read-only property.(2540)

If we want to convert a ReadonlyArray back to a writable array, we can use the type assertion as operator to convert it back to a regular array:

let arr = readOnlyArray as number[];

Conclusion

TypeScript interfaces are very handy for defining contracts within our code. We can use it to designate types of variables and function parameters. They let us know what properties a variable can take on, and whether they’re required, optional, or read-only. We can define interfaces with the interfaces keyword, optional properties with the question mark after a variable name, and the readonly keyword for read-only properties.

Categories
TypeScript

Introduction to TypeScript Enums

We look at how to define and use enums in TypeScript.

We look at how to define and use enums in TypeScript

If we want to define constants in JavaScript, we can use the const keyword. With TypeScript, we have another way to define a set of constants call the enums. Enums let us define a list of named constants. It’s handy for defining an entity that can take on a few possible values. TypeScript provides both numeric and string-based enums.

Numeric Enums

TypeScript has an enum type that’s not available in JavaScript. An enum type is a data type that has a set named values called elements, members, enumeral or enumerator of the type. They’re identifiers that act like constants in the language. In TypeScript, a numeric enum has a corresponding index associated with it. The members start with the index 0 by default, but it can be changed to start at any index we like and the subsequent members will have indexes that increment from that starting number instead. For example, we can write the following code to define a simple enum in TypeScript:

enum Fruit { Orange, Apple, Grape };

We can use enums by accessing the members like any other property. For example, in the Fruit enum, we can accept the members like in the following code:

console.log(Fruit.Orange);
console.log(Fruit.Apple);
console.log(Fruit.Grape);

Then console.log from the code above should get us 0 since we didn’t specify a starting index for the enum. We can specify the starting index of an enum with something like in the following code:

enum Fruit { Orange = 1, Apple, Grape };
console.log(Fruit.Orange);
console.log(Fruit.Apple);
console.log(Fruit.Grape);

Then we get the following logged from each console.log statement in order:

1
2
3

We can specify the same index for each member, but it wouldn’t be very useful:

enum Fruit { Orange = 1, Apple = 1, Grape };
console.log(Fruit.Orange);
console.log(Fruit.Apple);
console.log(Fruit.Grape);

Then we get:

1
1
2

from the console.log . As we can see, we specify the index pretty much however we want to change it. We can even have negative indexes:

enum Fruit { Orange = -1, Apple, Grape };
console.log(Fruit.Orange);
console.log(Fruit.Apple);
console.log(Fruit.Grape);

Then we get:

-1
0
1

from the console.log . To get an enum member by its index, we can just use the bracket notation like we access array entries by its index. For example, we can write the following code:

enum Fruit { Orange, Apple, Grape };
console.log(Fruit[0]);
console.log(Fruit[1]);
console.log(Fruit[2]);

Then we get:

Orange
Apple
Grape

Numeric enums can have computed values assigned to their members. For example, we can write a function to get a value for each enum member like in the following code:

const getValue = () => 2;

enum Fruit {
  Orange = getValue(),
  Apple = getValue(),
  Grape = getValue()
};

Note that we assigned a return value for each member. If we don’t do that for all of them like in the following code:

const getValue = () => 2;

enum Fruit {
  Orange = getValue(),
  Apple = getValue(),
  Grape
};

Then the TypeScript compiler won’t compile the code and will give an error “Enum member must have initializer.(1061)“. We can mix both constant and computed values in one enum, so we can write something like:

const getValue = () => 2;

enum Fruit {
  Orange = getValue(),
  Apple = 3,
  Grape = getValue()
};

String Enums

TypeScript enum members can also have string values. We can set the values of each member to a string by assigning strings to them like in the following code:

enum Fruit {
  Orange = 'Orange',
  Apple = 'Apple',
  Grape = 'Grape'
};

However, unlike numeric enums, we can’t assign computed values to them. For example, if we have the following:

const getValue = () => 'Orange';

enum Fruit {
  Orange = getValue(),
  Apple = 'Apple',
  Grape = 'Grape'
};

Then we would get the TypeScript compiler error message “Computed values are not permitted in an enum with string-valued members. (2553)” since computed values aren’t allowed for string-valued enums. String enums don’t have auto-incrementing behavior like numeric enums since they don’t have numerical values, but the values of the enum members are much clearer since each value is a meaningful value that’s clear to any human reading it.

In a single enum, we can have some members having numeric values and others having string values like in the following code:

enum Fruit {
  Orange = 2,
  Apple = 'Apple',
  Grape = 'Grape'
};

However, this is more confusing than having a single type of value for all enum members, so it’s not recommended to have mixed value types for different members for an enum.

Computed and Constant Members

Each enum member has a value associated with it which can either be constant or computed. An enum member is constant if the first member in the enum has no value explicitly assigned to it, which means that it’s assigned the value 0 by default. It can also be considered constant if it doesn’t have an explicit value assigned to it and the preceding enum member was a numeric constant, which means that it’ll have the value of the preceding member plus one. For example, if we have:

enum Fruit { Orange = 1, Apple, Grape };

Then Apple and Grape are both constant members since it’ll be automatically assigned the values 2 and 3 respectively. They’re also considered constant if each member has string values assigned to them. Also, if an enum references a previously defined enum member which can be from the same or a different enum. The return value of any operation assigned to constant enums like surrounding an enum expression with parentheses, doing unary arithmetic or bitwise operations to an enum expression like +, -, ~ , or doing binary arithmetic or bitwise operations like, -, *, /, %, <<, >>, >>>, &, |, ^ with enum expressions as operands are all considered constant enum expressions.

For example, the following enum is an enum with constant enum expressions:

enum Fruit {
  Orange = 1 + 2,
  Apple =  1 + 3,
  Grape = 1 + 4
};

The expressions are constant since they’re computed from any variable or return values of functions. Each member has values that’s computed from number values, rather than numbers assigned to variables or returned from functions.

The following is also an example of an enum with constant members:

enum Fruit {
  Orange = 1 + 2,
  Apple =  1 + 3,
  Grape = Orange + Apple
};

All the members including the last one are constant since the value of Grape is computed from Orange and Apple which are constant. Bitwise operations with both operands being constant values are also considered constants as we have in the following code:

enum Fruit {
  Orange = 1 | 2,
  Apple =  1 + 3,
  Grape = 'abc'.length
};

Anything else not described above is considered computed values. For example, if we have:

enum Fruit {
  Orange = 1 + 2,
  Apple =  1 + 3,
  Grape = 'abc'.length
};

Then Grape is a computed member since the expression we assigned to Grape is not computed from any constant member, and it involves getting a property from an object, which isn’t computed from a constant value.

If we want to define constants in JavaScript, we can use the const keyword. With TypeScript, we have another way to define a set of constants call the enums. Enums let us define a list of named constants. It’s handy for defining an entity that can take on a few possible values. TypeScript provides both numeric and string-based enums. TypeScript allows enum members to have numeric and string values. They can also be computed from values for other enum members or from any other expression we wish to assign. Constant enums are the ones that are computed from actual numerical values as operands or with actual values assigned to the member. All other values are computed member values.