DEV Community

Cover image for Learning TypeScript with React - Part 2 (The what, why and how of interfaces)
Ana Liza Pandac
Ana Liza Pandac

Posted on

Learning TypeScript with React - Part 2 (The what, why and how of interfaces)

⚡TL;DR: Understanding what are interfaces, why use them and how to use them can help you in writing more reusable code and creating awesome applications.

In the previous article, we talked about types, what is it, how to use them and why use them. If you haven't read it yet, I recommend that you read it first.

In this post, we'll get to know what is an interface, why should you care about it, and how to use it to write more reusable code.

Let's begin.

What is an interface?

Interfaces are the core way in TypeScript to compose multiple type annotations into a single named annotation.

It’s a way of creating a new type that describes an object’s property names and their types. Remember the types that we covered before (number, boolean, string, etc.)? An interface is the same as those types.

Why should you care about interfaces?

For me, interfaces are one of the best features of the TypeScript language. Let me show you just how awesome it is.

Let's start with the following example.

You have a function called renderVideo which accepts video as a parameter in which you annotated with all the properties that should be present in a video object.

const video = {
  videoId: '',
  title: '',
  description: '',
  thumbnailURL: ''
};

const renderVideo = (video: { videoId: string; title: string; description: string; thumbnailURL: string }): void => {

}
Enter fullscreen mode Exit fullscreen mode

Even though the parameter annotation is quite long, the code above is okay, the function parameter and return value was annotated correctly.

However, imagine if you added more functions which accepts a video.

const video = {
  videoId: '',
  title: '',
  description: '',
  thumbnailURL: ''
};

const renderVideo = (video: { videoId: string; title: string; description: string; thumbnailURL: string }): void => {

}

const getVideoSummary = (video: { videoId: string; title: string; description: string; thumbnailURL: string }): void => {

}

// another function has a `video` parameter
// another one
// and another one
// *shouts* "DJ Khaled"
Enter fullscreen mode Exit fullscreen mode

Aaahhhh. I don't know about you but to me, the code above doesn't look good.

There are a lot of long annotations which are duplicated several times. The code is harder to read as well.

Now imagine again (we're imagining a lot here) that you need to do either one or more of the following changes to a video:

  • add the date it was published as a new property
  • rename thumbnailURL to imageURL
  • change the type of videoId from string to a number

Applying these new changes to the code above means you need to change all the video annotations in a lot of places.

That's a lot of work and we don't want that so how can we solve this?

Simple, by using an interface.

How to create an interface?

We can convert this long video annotation into an interface called Video.

To create an interface, use the interface keyword followed by a name that represents the object, in this case Video.

The name should start with a capital letter just like how we create React components and a set of curly braces where you declare all of the object's properties and methods and its types.

This is how you create the Video interface.

interface Video {
  videoId: string;
  title: string;
  description: string;
  thumbnailURL: string;
}
Enter fullscreen mode Exit fullscreen mode

How do you use an interface?

Since an interface is a custom type, we can annotate it to a variable just like a normal type.

let item: Video;
Enter fullscreen mode Exit fullscreen mode

By using the Video interface, we can rewrite the previous example to this:

interface Video {
  videoId: string;
  title: string;
  description: string;
  thumbnailURL: string;
}

const renderVideo = (video: Video): void => {};
const getVideoSummary = (video: Video): void => {};

// another function that uses a video
// another one
// and another one

const video = {
  videoId: "",
  title: "",
  description: "",
  thumbnailURL: ""
};

renderVideo(video);
Enter fullscreen mode Exit fullscreen mode

This code looks wayyyyy better than the previous one right?

It looks cleaner and in one glance, we can see right away that the parameter is a type of Video.

Most of all, we can add, remove or rename a property or change a property type only in one place, the interface declaration. Awesome!

💡 If you use VS code, which I highly recommend you should, you can see all the property names and types of an interface by holding the ctrl/cmd key and hovering over the interface name.

Pretty cool right?

How are interfaces used in the app?

Aside from using the Video interface, I created another interface to represent how the Youtube API response is structured.

interface YoutubeVideo {
  id: { videoId: string };
  snippet: {
    publishedAt: string;
    title: string;
    description: string;
    thumbnails: { medium: { url: string }; default: { url: string } };
    channelTitle: string;
    channelId: string;
  };
}

interface YoutubeAPIResponse {
  data: { items: YoutubeVideo[] };
}
Enter fullscreen mode Exit fullscreen mode

This interface is used in the fetchChannelVideos function.

export const fetchChannelVideos: (
  args: FetchVideosArguments
) => Promise<Video[]> = async ({
  shouldUseDefaultVideos = false,
  searchQuery
}) => {
 const requestURL = '';
 const response:YoutubeAPIResponse = await axios.get(requestURL);
};
Enter fullscreen mode Exit fullscreen mode

Since an interface is a type, it means the TS compiler also does error-checking on the annotated data.

By creating an interface for the API response structure and annotating it to the variable, we made sure that we don't accidentally access a property that does not exist.

So if we try to access comments, which is not included in the API response, the TypeScript compiler will give us an error.

Writing Reusable Code with Interfaces

Now that we covered the what, why and how of interfaces, let's talk about how we can use it to write more reusable code.

Let's say you have a component that renders comments from a Youtube user and the channel.

First we write it like this:

interface User { name: string; avatar: string; }
interface Channel { name: string; avatar: string; }

const renderUserComment: (user: User, comment: string) => React.ReactNode = (
  user,
  comment
) => { /* return user comment */ };

const user = { name: "Ana", avatar: "ana.png" };
const channel = { name: "Tedx Talks", avatar: "tedxtalks.png" };

const renderChannelComment: (
  channel: Channel,
  comment: string
) => React.ReactNode = (channel, comment) => {/* return channel comment */};

renderUserComment(user, 'hello there');
renderChannelComment(channel, 'hi, thanks for watching');
Enter fullscreen mode Exit fullscreen mode

There are two separate interfaces to represent a user and a channel as well as separate functions to render these comments even though they do the same thing, rendering a comment with the author.

How can we rewrite this to remove the duplicated code?

Although we can use a union type to indicate that the comment author can be a User or a Channel,

const renderComment: (author: User|Channel, comment: string) => React.ReactNode = (
  author,
  comment
) => { /* return comment */ };
Enter fullscreen mode Exit fullscreen mode

In this scenario, it's better to create one interface to represent the two types of users.

Let's rewrite it using a more generic interface called Author.

interface Author { name: string; avatar: string; }

const renderComment: (author: Author, comment: string) => React.ReactNode = (
  author,
  comment
) => { /* return comment */};

const user = { name: "Ana", avatar: "ana.png", github: "analizapandac" };
const channel = { name: "Tedx Talks", avatar: "tedxtalks.png", channelId: "UCsT0YIqwnpJCM-mx7-gSA4Q" };

renderComment(user, 'hello there');
renderComment(channel, 'hi, thanks for watching');
Enter fullscreen mode Exit fullscreen mode

Even though user and channel represents two different types of data, they satisfy the requirements to be considered an author which is a name and avatar so the renderComment function works for both objects.

The examples above are very basic but the point here is that we can use a generic interface to represent different objects which can work with different functions. This is one way to use interfaces to write more reusable code.

Closing Thoughts

Aside from learning about the what, why and how of interfaces, I hope that I have also shown you a glimpse of the power of interfaces and how you can use it to create amazing applications.

There's a lot more to interfaces especially on how to use it together with the other TypeScript features such as classes and write even better code.

I'll continue learning the more advanced TS concepts, creating new exciting projects using them and sharing them with you.

I hope you'll do the same 😊

Project Github Repo

Here's the source code of the app: https://github.com/analizapandac/tedflix

You can clone it and play with the source code on your local if you want.

You can see the deployed app at https://tedflix.netlify.com/.


Here's the previous article:


Let me know in the comments below if you have any feedback and suggestions or pointers on where to go next. Thank you in advance.

Top comments (4)

Collapse
 
yawaramin profile image
Yawar Amin

Great article, interfaces are a very interesting topic for me. I wrote something about them here dev.to/yawaramin/interfaces-for-sc...

Collapse
 
analizapandac profile image
Ana Liza Pandac

Thank you Yawar. The article you wrote has a lot of good points as well especially when it comes to data abstraction and testing. I didn't know until now that Flow and TypeScript has a very similar syntax so that's really good to know, I might try it out as well and see.

Collapse
 
ahmednrana profile image
Rana Ahmed

Very good article. Waiting for the next article

Collapse
 
arieloo profile image
Ariel

thx