Omitting properties in TypeScript


While working on a project I came across a type issue I hadn’t yet encountered. I was creating a ModalContext that would allow me to toggle mantine modals within the app without needing to declare Modal repeatedly. (Fyi, mantine has its own built in Modals manager, but unfortunately I hadn’t yet discovered that.)

This ModalContext contained a toggleModal function that took in the following params and was defined as follows:

interface ToggleModalParams {
  isVisible: boolean /*Determines if the modal is open or closed*/;
  content?: React.ReactNode /*Content to display inside the modal*/;
  props?: ModalProps /*Props to pass to the modal*/;
}

const toggleModal = ({ isVisible, content, props = {} }: ToggleModalParams) => {
  setIsOpen(isVisible);
  if (Object.keys(props).length) {
    setModalProps(props);
  }
  if (content) {
    setModalContent(content);
  }
};

Inside the context provider I rendered a mantine Modal and manually opened and onClose props. This was done to make sure that simplify the props I would need to pass to the Modal component:

import React, { createContext, useState } from "react";
import { Modal, ModalProps } from "@mantine/core";

const [isOpen, setIsOpen] = useState<boolean>(false);
const [modalProps, setModalProps] = useState<ModalProps>({});
const [modalContent, setModalContent] = useState<React.ReactNode>(null);

<ModalContext.Provider value={{ toggleModal }}>
  <Modal
    {...modalProps}
    opened={isOpen}
    onClose={() => {
      toggleModal({ isVisible: false });
    }}
  >
    {modalContent}
  </Modal>
  {children}
</ModalContext.Provider>;

I setup the context and attempted to use the toggleModal function for the first time:

const toggleRandomModal = () =>
  toggleModal({
    isVisible: true,
    content: <>Hi there! 😎</>,
    props: {
      title: "Am I a modal?",
      centered: true,
      size: 300,
    },
  });

This seemed all and well, but I noticed the following type error was occurring for the “props” param:

(property) ToggleModalParams.props?: ModalProps | undefined

Type '{ title: string; centered: true; size: number; }' is missing the
following properties from type 'ModalProps': opened, onClose ts(2739)

ModalContext.tsx: The expected type comes from property 'props' which
is declared here on type 'ToggleModalParams'

Ah snap 🥲. The ModalProps type I was importing from @mantine/core explicitly requires “opened” and “onClose”. As a result TypeScript was unhappy when I tried to pass in a props object that didn’t have those fields. It was very tempting to use //@ts-ignore and be done with it, but I typically want to avoid anti-patterns and it would get very annoying to have to add //@ts-ignore every time I used this function.

After some searching, I discovered the Omit utility type. As it is nicely described in the TypeScript documentation, this utility type allows you to omit certain keys from a type. This meant I could create my own IModalProps type that omitted the opened and onClose fields:

import React from "react";
import { ModalProps } from "@mantine/core";

type IModalProps = Omit<ModalProps, "onClose" | "opened">;

interface ToggleModalParams {
  isVisible: boolean;
  content?: React.ReactNode;
  props?: IModalProps;
}

Nice! Now I could pass in a props object that doesn’t have those fields and TypeScript will happily accept it.

Thanks for reading! 👋

References

  1. mantine
  2. Omit utility type