SJoshi
SJoshi

Reputation: 1976

How to call a function from ImmutableJS Record constructor

I've replaced many/most of my typescript entities with Immutable Records for some level of safety/security/warm and fuzzies but I just noticed that my constructor is now broken.

Originally, I was automatically creating new UUIDs in my constructor as a default value, but with the ImmutableJS Records - this behaviour is broken.

I do understand why, but I'm not entirely sure what the correct workaround is - but I feel like it's either really complicated, or stupidly simple.

import { Record } from "immutable";
import uuid from "uuid";

const tagDefaults: TagParams = {
    depth: 0,
    id: "tag::" + uuid.v4(),
    name,
    order: 0,
};

interface TagParams {
    name: string;
    order: number;
    depth: number;
    id: string;
}

export class Tag extends Record(tagDefaults) { }

Creating that initial tagDefaults is what creates the first UUID - after which, all subsequent new Tag()'s use the same ID.

Is there a simple way to have a function called on each constructor? I've tried overriding in the constructor (this.id = uuid.v4()) but that actually causes Webpack to crap out on me.

UPDATE: June 8th, 2018

Using the answer @mpontus provided, here is an updated example showing that either option can work.

import { Record, Set } from "immutable";
import uuid from "uuid";

const tagDefaults: TagParams = {
    depth: 0,
    id: "",
    name,
    order: 0,
};

interface TagParams {
    name: string;
    order: number;
    depth: number;
    id: string;
}

export class Tag extends Record(tagDefaults) {
    constructor(props: Partial<TagParams> = {}) {
        // Option A - Works
        if (!props.id) {
            props.id = uuid.v4();
        }

        super(props);

        // Option B - Works
        // if (!this.id) {
        //     return this.set("id", uuid.v4());
        // }
        // return this;
    }
}

describe("Given a Tag", () => {
    describe("When constructed", () => {
        test("It should contain a unique id", () => {
            const tag1 = new Tag();
            const tag2 = new Tag({});
            const tag3 = new Tag({ depth: 1, name: "hello", order: 10 });
            const tag4 = new Tag({ id: "tag4Id" });
            const tags = Set([tag1, tag2, tag3, tag4].map((t) => t.id));
            expect(tags.size).toBe(4);
            console.log([tags]);
        });
    });
});

Upvotes: 1

Views: 342

Answers (1)

mpontus
mpontus

Reputation: 2203

You can't accomplish what you want using default values.

Your attempt to override Tag constructor was good, but you have to override the values that go into the constructor:

const tagDefaults = {
    depth: 0,
    id: "",
    name: "",
    order: 0,
};

class Tag extends Record(tagDefaults) {
    constructor(values) {
        const finalValues = { ...values };

        if (finalValues.id === undefined) {
            finalValues.id = uuid.v4();
        }

        super(finalValues);
    }
}

Alternatively, you can return a different record instance from the constructor, but I'm not sure if TypeScript will accept that.

const { Record } = require("immutable");
const uuid = require('uuid');

const tagDefaults = {
    depth: 0,
    id: undefined,
    name: "",
    order: 0,
};

class Tag extends Record(tagDefaults) {
    constructor(values) {
        super(values);

        if (this.id === undefined) {
            return this.set('id', uuid.v4());
        }

        return this;
    }
}

Upvotes: 1

Related Questions