Record

A record is similar to a JS object, but enforces a specific set of allowed string keys, and has default values.

The Record() function produces new Record Factories, which when called create Record instances.

Records always have a value for the keys they define. removeing a key from a record simply resets it to the default value for that key.

myRecord.get('a'); // 1
myRecord.get('b'); // 3
const myRecordWithoutB = myRecord.remove('b');
myRecordWithoutB.get('b'); // 2

Values provided to the constructor not found in the Record type will be ignored. For example, in this case, ABRecord is provided a key "x" even though only "a" and "b" have been defined. The value for "x" will be ignored for this record.

const myRecord = ABRecord({ b: 3, x: 10 });
myRecord.get('x'); // undefined

Because Records have a known set of string keys, property get access works as expected, however property sets will throw an Error.

Note: IE8 does not support property access. Only use get() when supporting IE8.

myRecord.b; // 3
myRecord.b = 5; // throws Error

Record Types can be extended as well, allowing for custom methods on your Record. This is not a common pattern in functional environments, but is in many JS programs.

However Record Types are more restricted than typical JavaScript classes. They do not use a class constructor, which also means they cannot use class properties (since those are technically part of a constructor).

While Record Types can be syntactically created with the JavaScript class form, the resulting Record function is actually a factory function, not a class constructor. Even though Record Types are not classes, JavaScript currently requires the use of new when creating new Record instances if they are defined as a class.

Typing Records:

Immutable.js exports two types designed to make it easier to use Records with typed code, RecordOf<TProps> and RecordFactory<TProps>.

When defining a new kind of Record factory function, use a type that describes the values the record contains along with RecordFactory<TProps>. To type instances of the Record (which the factory function returns), use RecordOf<TProps>.

Typically, new Record definitions will export both the Record factory function as well as the Record instance type for use in other code.

import type { RecordFactory, RecordOf } from 'immutable';

// Use RecordFactory<TProps> for defining new Record factory functions.
type Point3DProps = { x: number, y: number, z: number };

const defaultValues: Point3DProps = { x: 0, y: 0, z: 0 };
const makePoint3D: RecordFactory<Point3DProps> = Record(defaultValues);
export makePoint3D;

// Use RecordOf<T> for defining new instances of that Record.
export type Point3D = RecordOf<Point3DProps>;
const some3DPoint: Point3D = makePoint3D({ x: 10, y: 20, z: 30 });

Typing Record Subclasses:

Records can be subclassed as a means to add additional methods to Record instances. This is generally discouraged in favor of a more functional API, since Subclasses have some minor overhead. However the ability to create a rich API on Record types can be quite valuable.

When typing Subclasses, do not use RecordFactory<TProps>, instead apply the props type when subclassing:

type PersonProps = { name: string; age: number };

const defaultValues: PersonProps = { name: 'Aristotle', age: 2400 };
const PersonRecord = Record(defaultValues);

class Person extends PersonRecord<PersonProps> {
  getName(): string {
    return this.get('name');
  }

  setName(name: string): this {
    return this.set('name', name);
  }
}

Choosing Records vs plain JavaScript objects

Records offer a persistently immutable alternative to plain JavaScript objects, however they're not required to be used within Immutable.js collections. In fact, the deep-access and deep-updating functions like getIn() and setIn() work with plain JavaScript Objects as well.

Deciding to use Records or Objects in your application should be informed by the tradeoffs and relative benefits of each:

  • Runtime immutability: plain JS objects may be carefully treated as immutable, however Record instances will throw if attempted to be mutated directly. Records provide this additional guarantee, however at some marginal runtime cost. While JS objects are mutable by nature, the use of type-checking tools like TypeScript or Flow can help gain confidence in code written to favor immutability.

  • Value equality: Records use value equality when compared with is() or record.equals(). That is, two Records with the same keys and values are equal. Plain objects use reference equality. Two objects with the same keys and values are not equal since they are different objects. This is important to consider when using objects as keys in a Map or values in a Set, which use equality when retrieving values.

  • API methods: Records have a full featured API, with methods like .getIn(), and .equals(). These can make working with these values easier, but comes at the cost of not allowing keys with those names.

  • Default values: Records provide default values for every key, which can be useful when constructing Records with often unchanging values. However default values can make using Flow and TypeScript more laborious.

  • Serialization: Records use a custom internal representation to efficiently store and update their values. Converting to and from this form isn't free. If converting Records to plain objects is common, consider sticking with plain objects to begin with.

Construction

Method signature

Record<TProps>(defaultValues: TProps, name?: string): Record.Factory<TProps>

Static methods

Method signature

Record.isRecord(maybeRecord: unknown): boolean

Method signature

Record.getDescriptiveName(): string

Reading values

Method signature

has(key: string): boolean

Method signature

get<K>(key: K): TProps[K]
get<T>(key: string, notSetValue: T): T

Reading deep values

Method signature

hasIn(key: string): boolean

Method signature

getIn<K>(key: K): TProps[K]
getIn<T>(key: string, notSetValue: T): T

Value equality

Method signature

equals(other: unknown): boolean

Method signature

hashCode(): number

Persistent changes

Method signature

set<K>(key: K, value: TProps[K]): this

Method signature

update<K>(key: K, updater: (value: TProps[K]) => TProps[K]): this;

Method signature

merge(...collections: Array<Partial<TProps>>>): this

Method signature

mergeDeep(...collections: Array<Partial<TProps>>): this;

Method signature

mergeWith(
  merger: (oldVal: unknown, newVal: unknown, key: keyof TProps) => unknown,
  ...collections: Array<Partial<TProps>>
): this;

Method signature

mergeDeepWith(
  merger: (oldVal: unknown, newVal: unknown, key: unknown) => unknown,
  ...collections: Array<Partial<TProps>>
): this;

delete§

Alias:

remove

Returns a new instance of this Record type with the value for the specific key set to its default value.

Method signature

delete<K extends keyof TProps>(key: K): this;

Returns a new instance of this Record type with all values set to their default values.

Method signature

clear(): this;

Deep persistent changes

Method signature

setIn(keyPath: Iterable, value): this;

Method signature

updateIn(keyPath: Iterable, updater: (value) => unknown): this;

Method signature

mergeIn(...collections: Array<Partial<TProps>>): this;

Method signature

mergeDeepIn(...collections: Array<Partial<TProps>>): this;

deleteIn§

Alias:

removeIn

Method signature

deleteIn(keyPath: Iterable<unknown>): this;

Conversion to JavaScript types

Deeply converts this Record to equivalent native JavaScript Object.

Note: This method may not be overridden. Objects with custom serialization to plain JS may override toJSON() instead.

Method signature

toJS(): DeepCopy<TProps>;

Shallowly converts this Record to equivalent native JavaScript Object.

Method signature

toJSON(): TProps;

Shallowly converts this Record to equivalent JavaScript Object.

Method signature

toObject(): TProps;

Transient changes

Note: Not all methods can be used on a mutable collection or within withMutations! Only set may be used mutatively.

See Map#withMutations

Method signature

withMutations(mutator: (mutable: this) => unknown): this;

See Map#asMutable

Method signature

asMutable(): this;

See Map#wasAltered

Method signature

wasAltered(): boolean

See Map#asImmutable

Method signature

asImmutable(): this;

Sequence algorithms

Method signature

toSeq(): Seq.Keyed<keyof TProps, TProps[keyof TProps]>;
;