The Complete Basics of Typescript Record

December 24, 2022
Faith Ajayi
React.js

The Fastest Way to Build React UI

Convert Figma designs to production-ready React.js code. Build stunning apps and landing pages faster than your peers and competitors.

Introduction

When attempting to incorporate more sophisticated types of data, TypeScript Records are an excellent technique to guarantee consistency. They allow you to provide unique interfaces for the values while enforcing important values.

When used to its full potential, TypeScript can help teams write better, less error-prone, more maintainable code whether used in the back-end or front-end. I guarantee you’ll be on your way to writing better code in lesser time.

If you use TypeScript, you should be familiar with Utility Types because they help your code be more readable and reusable. In this article, we’ll show you how to use Record Utility Type in your applications with examples.

Before we continue, let me introduce you to our CopyCat tool which helps you accelerate your development time. It accomplishes this by converting your Figma files into fully functional React applications. Not only that, but it also supports your favorite tools such as Typescript, Bootstrap, and TailwindCSS, to name a few! Click here now and thank me later.

To get the most out of this article, it is important to have a basic knowledge of;

What is a TypeScript Record Utility Type?

Use a TypeScript Record utility type to make a type that represents a map from keys to values. It enables the creation of types that specify both the types of values connected to known keys and the structure of an object with those known keys.

Here’s an example of using the Record utility type:

type User = {
  id: number;
  name: string;
  email: string;
};

type UserMap = Record<string, User>;

const users: UserMap = {
  "123": {
    id: 123,
    name: "John Doe",
    email: "johndoe@example.com"
  },
  "456": {
    id: 456,
    name: "Jane Doe",
    email: "janedoe@example.com"
  }
};

To represent a user in this example, we first define a User type with fields for an ID, name, and email address. The UserMap type, which represents a map from strings to User objects, is then created using the utility type Record. A map of user IDs to User objects is then initialized into a user variable of type UserMap.

For creating types for objects with a known set of keys but dynamic values, the Record utility type is helpful. It enables you to guarantee that the object’s structure follows a specific pattern while still allowing the values linked to each key to be of various types.

However, one of the primary advantages of using the Record utility type is that you can specify the types of keys and values in a map. We used a string type for the keys and a User type for the values in the preceding example. This means that any UserMap-compliant object must have string keys and User values.

Consistent Structures

Another advantage of the Record utility type is that it aids in the establishment of a consistent structure for the objects it represents. If you try to add a key-value pair to the users object in the preceding example that does not have a string key or a User value, the TypeScript compiler will throw an error. This allows you to detect errors earlier and ensures that your code is correct and consistent.

Here is another example that shows how you can use the Record utility type to create a type for a JSON object:

type JSONObject = Record<string, string | number | boolean | null | JSONObject>;

const json: JSONObject = {
  "string": "hello world",
  "number": 123,
  "boolean": true,
  "null": null,
  "object": {
    "foo": "bar"
  }
};

In this example, we use a Record utility type to create a `JSONObject` type that symbolizes a JSON objec. We stipulate that the values may be strings, numbers, booleans, null, or other `JSONObject` objects, and the keys must be strings. This enables us to design a type that is both flexible and strict for JSON objects.

Understanding and making use of the Record Type in TypeScript

As discussed earlier in this article, the Record type in TypeScript allows you to create an object type with a fixed set of keys and values. Also, there are some key points we should keep in mind to help in understanding and making use of the typescript record types.

Most importantly it is important to know that you can specify the type of keys and values when using the Record type. For example, you can specify that the keys must be strings and the values must be numbers.

Also, the Record type is useful for creating object types with a fixed set of properties, mapping over the properties of an object, creating object types with optional properties, and creating object types with such properties (read-only). You can combine the Record type with other types, such as the Readonly type, to create more complex object types.

Here is an example that demonstrates some of these points:

type User = Record<string, string | number> & Readonly<{}>;

const user: User = {
  name: "John Doe",
  age: 30,
  email: "johndoe@gmail.com"
};

// Attempting to modify a readonly property values will result in an error
user.name = "Jane Doe"; // Error: Cannot assign to 'name' because it is a read-only property

// Map over the properties of the user object
Object.keys(user).forEach(key => {
  const value = user[key];
  // Do something with the property keys and values
  console.log(`${key}: ${value}`);
});

In this example, we create a User type by combining the typescript Record type and the Readonly type. This states that the object must have read-only properties, type string as keys, and values that are either type strings or numbers. We then produce a user object that complies with this type, demonstrates how to map over its properties, and demonstrates how an error is raised when a read-only property is attempted to be modified.

However, we would be discussing the four use cases below for understanding and using the Record type ;

1. Creating an object type with a fixed set of properties:

type User = Record<string, string | number>;

const user: User = {
  name: "John Doe",
  age: 30,
  email: "johndoe@gmail.com"
};

The Record type, which mandates that the object must have string keys and values that are either strings or numbers, is used in this example to create a User type. After that, a user object that complies with this type is created.

2. Mapping over the properties of an object:

type User = Record<string, string | number>;

const user: User = {
  name: "John Doe",
  age: 30,
  email: "johndoe@gmail.com"
};

// Map over the properties of the user object
Object.keys(user).forEach(key => {
  const value = user[key];
  // Do something with the key and value
  console.log(`${key}: ${value}`);
});

In this example, we use the Object.keys() method to iterate over the properties of the user object, and we use the Record type to ensure that the object has the correct structure.

3. Creating an object type with a fixed set of optional properties:

type User = Record<string, string | number | undefined>;

const user: User = {
  name: "John Doe",
  age: 30,
  email: "johndoe@gmail.com"
};

In this example, we create a User type using the Record type, which specifies that the object must have string keys and values that are either strings, numbers, or undefined. This allows us to create objects with a fixed set of optional properties.

4. Creating an object type with a fixed set of read-only properties:

type User = Record<string, string | number> & Readonly<{}>;

const user: User = {
  name: "John Doe",
  age: 30,
  email: "johndoe@gmail.com"
};

// Attempting to modify a readonly property will result in an error
user.name = "Jane Doe"; // Error: Cannot assign to 'name' because it is a read-only property

In this example, we create a User type by combining the Record and Readonly types. This specifies that the object must have string keys and string or number values and that all properties are read-only. This means that modifying a property will result in an error.

When to use a Record and an index Signature in TypeScript?

Being fully aware that the creation of a Record, an object type with a predetermined set of keys and values means that any object of this type must have both the required keys and the required types for the values of those keys. For instance, you might use a Record to develop a type for a user object that needs to include the following information: name, age, and email address.

type User = Record<string, string | number>;

The Record type, which mandates that the object must have string keys and values that are either strings or numbers, is used in this example to create a User type.

The types of properties on an object that are unknown at the time of creation can be specified using an index signature. To create a type for an object that can have any number of properties but all of those properties’ values must be numbers, for instance, you could use an index signature as follows:

interface Data {
  [key: string]: number;
}

In this example, we create a Data interface that has an index signature that specifies that any property on the object must have a value that is a number.

So, when should you use a Record and when should you use an index signature?

  1. If you want to create an object type with a fixed set of keys and values, you should use a Record.
  2. If you want to create an object type that can have any number of properties, but the values for those properties must have a specific type, you should use an index signature.

Here is an example that shows how to use both a Record and an index signature to create a complex object type:

type User = Record<string, string | number>;

interface Data {
  [key: string]: number;
}

type UserData = User & Data;

const userData: UserData = {
  name: "John Doe",
  age: 30,
  email: "johndoe@gmail.com",
  score: 90,
  points: 100
};

In this example, we create a User type from the Record type that mandates that an object must have string keys and either strings or numbers for its values. Then, we create a Data interface that specifies that any property on the object must have a value that is a number using an index signature. The UserData type specifies that an object must have the properties specified by the User type and can have any additional properties with values that number.

Finally, we combine the User and Data types using the & operator to create this type. After that, a userData object that complies with this type is created.

Difference between Records and Objects in TypeScript?

A record is a type that describes an object with a fixed set of keys and values, whereas an object is a value that can have any properties and values. This is the primary distinction between records and objects in TypeScript.

Here is an illustration showing how a record differs from an object:

type User = Record<string, string | number>;

const user: User = {
  name: "John Doe",
  age: 30,
  email: "johndoe@gmail.com"
};

const data = {
  score: 90,
  points: 100
};

In this example, we use the Record type to define a User type that requires an object to have string keys and string or number values. We then construct a user object of this type. We also make a data object with any number of properties and values.

Error in Record Example

A record has a fixed set of keys and values, whereas an object can have any properties and values. This is one of the main distinctions between a record and an object. This means that you can specify the precise structure of an object using a record, but not with an object. For instance, you will receive an error if you attempt to assign a value to a property that does not exist on a record. The code illustration for this can be seen below;

type User = Record<string, string | number>;

const user: User = {
  name: "John Doe",
  age: 30,
  email: "johndoe@gmail.com"
};

user.name = "Jane Doe"; // This is allowed
user.score = 90; // Error: Property 'score' does not exist on type 'User'

In this example, we use the Record type to create a User type, and then we create a user object that conforms to this type. We then attempt to assign a value to the name property, which is permissible because it is present on the User type. We also attempt to assign a value to the score property, which does not exist on the User type, which results in an error.

A record is a type, whereas an object is a value, which is another important distinction between the two terms. This means that while you can specify the type of an object using a record, you cannot do the same with an object. You could use a record, for instance, to mandate that a function accept an object with a particular set of keys and values as an argument, as in the following example:

type User = Record<string, string | number>;

function printUser(user: User) {
  console.log(`Name: ${user.name}`);
  console.log(`Age: ${user.age}`);
  console.log(`Email: ${user.email}`);
}

const user: User = {
  name: "John Doe",
  age: 30,
  email: "johndoe@gmail.com"
};

printUser(user);

In this example, we create a User type using the Record type, and we create a printUser() function that takes an object of type User as an argument. We then create a user object that conforms to the User type and passes it to the printUser.

Difference between Record Type and Union Type?

Types

The Union type is used to specify a value that can have one of several possible types, whereas the Record type is used to create an object type with a fixed set of keys and values. This is the difference between the Record type and the Union type in TypeScript.

Here is an illustration showing how the Record type and the Union type differ from one another:

type User = Record<string, string | number>;
type UserId = string | number;

const user: User = {
  name: "John Doe",
  age: 30,
  email: "johndoe@gmail.com"
};

const userId: UserId = 12345;

In this example, we create a User type from the Record type that mandates that an object must have string keys and either strings or numbers for its values. After that, a user object that complies with this type is created. We also create a UserId type that specifies that a value may be either a string or a number using the Union type. The userId variable is then created and is of this type.

The Record type and the Union type differ in that the Record type specifies the structure of an object, whereas the Union type specifies the type of a value. This means that while the Record type can be used to ensure that an object has a specific set of keys and values, the Union type cannot. For example, if you try to access a property on a record that does not exist, you will receive an error message similar to this:

type User = Record<string, string | number>;

const user: User = {
  name: "John Doe",
  age: 30,
  email: "johndoe@gmail.com"
};

console.log(user.name); // This is allowed
console.log(user.score); // Error: Property 'score' does not exist on type 'User'

In this example, we use the Record type to create a User type, and then we create a user object that conforms to this type. We then attempt to access the name property, which is permitted because it is present on the User type. We also attempt to access the score property, which does not exist on the User type, resulting in an error.

Type Specification

Another significant distinction between the Record and Union types is that the Record type specifies the type of an object, whereas the Union type specifies the type of a value. This means that, unlike the Union type, you can use the Record type to specify that a function must take an object with a specific set of keys and values as an argument.

For example, you can use the Record type to specify that a function must take as an argument an object with a name, age, and email property, as shown below:

type User = Record<string, string | number>;

function printUser(user: User) {
  console.log(`Name: ${user.name}`);
  console.log(`Age: ${user.age}`);
  console.log(`Email: ${user.email}`);
}

const user: User = {
  name: "John Doe",
  age: 30,
  email: "j

Conclusion

During the course of this article, we have been able to look at what the TypeScript Record Utility Type is, how to understand and make use of the Record Type in TypeScript and when to use a Record and an index Signature in TypeScript. We also looked at the differences between Records and Objects in TypeScript and the differences between Record Type and Union Type.

Last but not least, visit our blog here for more fantastic posts like this one. By converting your Figma files to a live React project, CopyCat is a solution that can help you cut your development time by roughly 35%. Check it out here.

Interesting articles from our blog.

Related Articles

  • React.js

    Render HTML Data to the DOM with React createElement

    Introduction React is an open-source JavaScript library developed by Facebook and it’s the most used and liked Frontend library by over 76% of developer respondents in The State of Frontend 2022. In this article, we'll take a deep dive into…

    November 29, 2022
  • React.js

    How to Create an Amazing Bootstrap Navbar Complete Guide

    Introduction The Bootstrap navbar or navigation bar is an essential component of a website. It allows the user to scroll from one section of a webpage to another, or navigate to a different web page entirely. In this post, we’ll…

    November 24, 2022

Convert Figma To React

Convert Figma designs to production-ready React.js code. Build stunning apps and landing pages faster than your peers and competitors.

Convert Design to Code