Search code examples
reactjstypescriptreduxcreate-react-appimmutable.js

Why super() cannot set on an immutable record?


I'm setting up a really simple new Redux store state.

I've "cloned" a Redux store I've created before and simplified it. I've tried first in just one state, reducer, selector and action, then I've tried creating a tree (like my older project) but nothing changed. I've checked the package.json and I see I've the same Redux, React, ReactRedux, Typescript, ecc... version.

import { Record } from "immutable";

export enum officeLocations {
    cityOne = "CITYONE",
    cityTwo = "CITYTWO",
    none = "NONE",
    cityThree = "CITYTHREE",
    cityFour = "CITYFOUR"
}

export interface ILocationInfo {
    location: officeLocations;
}

const LocationInfoRecord = Record({
    location: officeLocations.none
});

export class LocationInfo extends LocationInfoRecord implements ILocationInfo {
    public location: officeLocations;

    constructor(props?: ILocationInfo) {
        // props ? super(props) : super(); <-- in older project I use this line instead of if-else... but here it tell me I cannot invoke super multiple times...
        if (props) {super(props);}
        else {super();}
        this.location = props ? props.location : officeLocations.none; // <- In my older code I don't need this line, for some reason it give me an error that location is not instantiated if I don't put this line
    }

    public with(values: Partial<ILocationInfo>) {
      return this.merge(values);
    }
}

I expect it works without any problem... but Typescript doesn't give me any problem and compile, but running it crash on "super()" saying "Error: Cannot set on an immutable record".

**

UPDATE:

**

Today, without changing anything, it will give me this error:

Type '(location: LocationInfo, action: IAction) => Map' is not assignable to type 'Reducer, any>'. Types of parameters 'LocationInfo' and 'state' are incompatible. Type 'Map | undefined' is not assignable to type 'LocationInfo'. Type 'undefined' is not assignable to type 'LocationInfo'

And now it give me this error even with my original code, the one explained with the comments on the code.

this error is given when I call "combineReducers".

    const settingsReducer = combineReducers({
       location: locationReducer,
       other: otherReducer
    });

Solution

  • Record is a factory, which produces immutable records. Your code tries to assign location on the factory itself, not the output of the factory.

    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.

    TypeScript does not deal with Immutable so well. Both want to have explicit definitions of the structure, but they do it on another level and in another way, making you write redundant code.

    So the location property is only for TS sake, it's not really used. We should not attempt to assign anything to it, so just tell TS that it's magically assigned:

    public readonly location!: officeLocations

    And then delete this.location = props ? props.location : officeLocations.none;

    If you want to mutate the input for some reason, just do it in the constructor and pass everything to super:

    constructor(values = {}, name) {
        values.location = values.location || officeLocations.none;
        super(values, name);
    }
    

    Here is an article with a description on how to use Immutable Records in TypeScript.