App.tsx

import React, { Component } from 'react';
import { QuarterPicker } from './QuarterPicker';
import { ClassPicker } from './ClassPicker';

interface AppState {
  quarter: string | undefined;  // quarter whose classes are being picked OR
                                // undefined if still picking quarter
}

// Top-level application that lets the user pick a quarter and then pick
// classes within that quarter.
export class App extends Component<{}, AppState> {
  constructor(props: {}) {
    super(props);
    this.state = {quarter: undefined};
  }

  render = (): JSX.Element => {
    if (this.state.quarter === undefined) {
      return <QuarterPicker onPick={this.setQuarter}/>;
    } else {
      return <ClassPicker quarter={this.state.quarter}
                          onBack={this.back}/>;
    }
  };

  setQuarter = (qtr: string): void => {
    this.setState({quarter: qtr});
  };

  back = (): void => {
    this.setState({quarter: undefined});
  };
}

QuarterPicker.tsx

import React, { Component, ChangeEvent } from 'react';
import { QUARTERS } from './classes';

interface QuarterPickerProps {
  onPick: (qtr: string) => void;  // called when a quarter is picked
}

// Displays UI that allows the user to choose a quarter.
export class QuarterPicker extends Component<QuarterPickerProps, {}> {
  render = (): JSX.Element => {
    const options: JSX.Element[] = [
      <option value="TBD">Pick a Quarter</option>
    ];
    for (let i = 0; i < QUARTERS.length; i++) {
      options.push(<option value={QUARTERS[i]}>{QUARTERS[i]}</option>);
    }

    return (
      <select onChange={this.handleChange}>
        {options}
      </select>);  // all buttons added as children
  }

  handleChange = (evt: ChangeEvent<HTMLSelectElement>): void => {
    if (evt.target.value !== "TBD")
      this.props.onPick(evt.target.value);
  };
}

ClassPicker.tsx

import React, { Component, ChangeEvent } from 'react';
import { CLASSES } from './classes';
import { TotalCredits } from './credits';

interface ClassPickerProps {
  quarter: string;    // quarter whose classes are being picked
  onBack: () => any;  // called when the user wants to pick a new quarter
}

interface ClassPickerState {
  classes: Array<string>;  // list of classes currently chosen
}

// UI that allows the user to choose classes within a quarter. Displays the
// number of credits for those classes.
export class ClassPicker extends Component<ClassPickerProps, ClassPickerState> {
  constructor(props: ClassPickerProps) {
    super(props);
    this.state = {classes: []};
  }

  render = (): JSX.Element => {
    const allClasses = CLASSES.get(this.props.quarter)!;

    let choices: JSX.Element[] = [];
    for (let i = 0; i < allClasses.length; i++) {
      const name = allClasses[i];
      const checked = this.state.classes.indexOf(name) >= 0;
      choices.push(
          <div className="form-check">
            <input className="form-check-input" type="checkbox"
                   value={allClasses[i]} checked={checked}
                   onChange={this.onChange} />
            <label className="form-check-label">{name}</label>
          </div>);
    }

    return (
        <div>
          <div>
            <button type="button" className="btn btn-link"
                    onClick={() => this.props.onBack()}>
              Back
            </button>
          </div>
          <p>Choose your classes:</p>
          <div className="choices">
            {choices}
          </div>
          <p>Total of {TotalCredits(this.state.classes)} credits.</p>
        </div>);
  };

  onChange = (evt: ChangeEvent<HTMLInputElement>): void => {
    // NOTE: We cannot directly mutate this.state.classes!
    //       We need to make a copy to pass to setState.
    const cls = evt.target.value as string;  // class name
    const index = this.state.classes.indexOf(cls);
    if (evt.target.checked) {
      if (index < 0) {
        this.setState({classes: this.state.classes.concat(cls)});
      }
    } else {
      if (index >= 0) {
        const before = this.state.classes.slice(0, index);
        const after = this.state.classes.slice(index+1);
        this.setState({classes: before.concat(after)});
      }
    }
  };
}

classes.tsx

// Quarters for which we have information.
export const QUARTERS = ["Fall 2020", "Winter 2021"];

// Classes available in each quarter.
export const CLASSES: Map<string, string[]> = new Map([
    ["Fall 2020", ["CSE 341", "CSE 344", "CSE 421", "CSE 431"]],
    ["Winter 2021", ["CSE 341", "CSE 344", "CSE 421", "CSE 444"]]
  ]);

credits.tsx

// Records the credits for each known course.
const CREDITS: Map<string, number> = new Map([
    ["CSE 341", 4],
    ["CSE 344", 4],
    ["CSE 421", 3],
    ["CSE 431", 3],
    ["CSE 444", 4],
  ]);


// Returns the total credits for the given list of classes. (The list should
// have no duplicates.)
export function TotalCredits(classes: string[]) {
  let total : number = 0;
  for (let i = 0; i < classes.length; i++) {
    if (CREDITS.has(classes[i])) {
      total += CREDITS.get(classes[i])!;
    } else {
      throw new Error("no such class: " + classes[i]);
    }
  }
  return total;
}

Full Code