TypeScript: use two different types

Hey guys, I’ve been thinking about types, I have an idea and I’d like to get your feedback.

Right now we are merging the types of a package with the types needed for actions and components.

For example, imagine this package:

import { Package } from "frontity/types";

interface MyPackage extends Package {
  state: {
    myPackage: {
      someState: string;
    };
  };
  actions: {
    myPackage: {
      someAction: Action<MyPackage>;
    };
  };
}

export default MyPackage;

If any of your actions or components need state.router.link then you need to add it to MyPackage as an optional:

import { Package } from "frontity/types";
import Router from "@frontity/router/types";

interface MyPackage extends Package {
  state: {
    router?: Router["state"]["router"]; // <- here
    myPackage: {
      someState: string;
    };
  };
  actions: {
    myPackage: {
      someAction: Action<MyPackage>;
    };
  };
}

export default MyPackage;

But this manual merges are:

  • Ugly. You have to do them manually.
  • They get verbose if you need to add multiple things like state, actions and libraries.
  • Unnecessary for the MyPackage type when you use it to type your package in src/index.js. I’d say they are even bad because you have optional types that are not really optional.
  • Unnecessary when you import this type in other packages. Because this types from other packages doesn’t belong to it. They are only there because they are used internally in some action or component.

I propose to use two different types in the types.ts file, one exclusively for the package, and the other a merge with the external packages for actions and components.

import { Package, Merge } from "frontity/types";
import Router from "@frontity/router/types";

interface MyPackage extends Package {
  state: {
    myPackage: {
      someState: string;
    };
  };
  actions: {
    myPackage: {
      someAction: Action<Types>;
    };
  };
}

export default MyPackage;

export type Packages = Merge<Router, MyPackage>;

I think it’s a cleaner approach, although it means people need to understand this a bit better because they need to use these to types in different places.

  1. They have to remember to use Packages in their Action and Derived.
someAction: Action<Packages>;
someDerived: Derived<Packages>;
  1. They have to remember to import MyPackage in the src/index.ts file:
import MyPackage from "../types";

const myPackage: MyPackage = {
  state: {
    myPackage: {
      someState: ”some state”
    }
  },
  actions: {
    myPackage: {
      someAction: ({ state }) => {
        // Can access state.router.link.
      }
    }
  }
}

export default myPackage;
  1. They have to remember to use Packages for their React components.
import { Connect } from "frontity/types";
import { Packages } from "../../types";

const Comp: React.FC<Connect<Packages>> = ({ state }) => {
  // Can access state.router.link.
};

Same for useConnect hook:

import { useConnect } from "frontity";
import { Packages } from "../../types";

const Comp: React.FC = () => {
  const { state } = useConnect<Packages>();
  // Can access state.router.link.
};

External packages will keep using the default import of types:

import MyPackage from "my-package/types";

But this time it will only contain the types of MyPackage and nothing else.

Maybe instead of Packages we can call it Types or just Frontity (because it is the whole Frontity for this package).


@david, is it possible to do the merge of the types with a Merge helper like this:

import { Package, Merge } from "frontity/types";
import Router from "@frontity/router/types";
import Source from "@frontity/source/types";

export type Packages = Merge<Router, Source MyPackage>;

Any thoughts? Feedback?

:point_up_2:

We can also try to find a good TypeScript merge tool, from libraries like https://github.com/pirix-gh/ts-toolbelt.

I think it totally makes sense! Also, changing the types this way shouldn’t affect to people using TypeScript at this moment.

The only thing is that we should show some examples in our docs to make clear why it should work like this.

I would prefer Packages, it’s the most meaningful name for me.

It is, although I would look for an external library as you proposed. Let me see what I find.

I have updated the OP to reflect that.

Awesome! Let’s try to do the switch then :slightly_smiling_face:

I want to do a deep look at the current Types on https://github.com/frontity/frontity/pull/415 so that may also be a good opportunity to do the switch.

Let me know what you find!

I did a quick test in our frontity.org repository and it looks like we can simply use the intersection operator to do a deep merge of all the packages.

Also, if some package depends on other packages (as Router and Source do) you can still pass all the merged packages to them in the same type definition.

export type Packages = FrontityOrg &
  Router<Packages> &
  Source<Packages> &
  Html2React &
  Analytics;
1 Like

Perfect, thanks @david.

Let’s do it then :slightly_smiling_face:

Do you all agree to use Packages? @mmczaplinski what do you think?

Packages sounds good to me :slight_smile:

1 Like