Reputation: 1600
Working on a React, Redux + Typescript project, I am trying to add Immutable JS to the stack.
I started with working on a large nested object that could really use being safer as an immutable data structure.
import { Record, fromJS } from "immutable";
const obj = {
name: "werwr",
overview: {
seasons: {
2017: [{ period: 1, rates: 2 }]
}
}
};
// -- Using fromJS
const objJS = fromJS(obj);
const nObj = objJS.getIn(["overview", "seasons", "2017"]);
console.log(nObj); // I get an immutable list cool!
// -- Using Record, infer the type
const objRecord = Record(obj)();
const nRec = objRecord.getIn(["overview", "seasons", "2017"]);
console.log(nRec); // but I get a JS array
// -- Using both
const makeRec = Record(objJS);
const bothRecord = makeRec({ name: "name" });
console.log(bothRecord); // fails
Runnable code in codesandbox: https://codesandbox.io/s/naughty-panini-9bpgn?file=/src/index.ts
Whats the proper way to convert such an object to a fully immutable data structure while not loosing the type? Thanks!
Upvotes: 0
Views: 807
Reputation: 5019
Record
is a factory for Record-Factories. As such, the argument should be an object template (aka default values), not actual data! (see docs).
const MyRecord = Record({
name: "werwr",
overview: null
});
const instance = MyRecord(somedata);
As you already noticed, the Record factory will not transform data to immutable. If you want to do that, you have to either do it manually with Maps
and Lists
, fromJS
or the constructor
of records.
The last approach is a bit weird, because then your record factory suddendly becomes a class:
const SeasonRecord = Record({
period: null, rates: null
})
class MyRecord extends Record({
name: "default_name",
seasons: Map()
}, 'MyRecord') {
constructor(values = {}, name) {
if(values.seasons) {
// straight forward Map of seasons:
// values = fromJS(values);
// Map of sub-records
values.seasons = Object.entries(values.seasons).reduce(
(acc, [year, season]) => {
acc[year] = SeasonRecord(season);
return acc;
}, {});
values.seasons = Map(values.seasons);
}
super(values, name);
}
}
const x = new MyRecord({
seasons: {
2017: { period: 1, rates: 2 }
}
})
console.log('period of 2017', x.seasons.get('2017').period)
I strongly suggest to not use unecessarily nest objects (record -> overview -> season) as it makes everything more complicated (and if you use large amounts of records, it might impact performance).
My general recommendation for Records is to keep them as flat as possible. The shown nesting of records allows to use the property access syntax instead of get
, but is too tendious most of the time. Simply doing fromJS() for the values of a record and then use getIn
is easier.
Upvotes: 0
Reputation: 1563
You can use classes to construct deep structures.
interface IRole {
name: string;
related: IRole[];
}
const roleRecord = Record({
name: '',
related: List<Role>(),
});
class Role extends roleRecord {
name: string;
related: List<Role>;
constructor(config: IRole) {
super(Object.assign({}, config, {
related: config.related && List(config.related.map(r => new Role(r))),
}));
}
}
const myRole = new Role({
name: 'President',
related: [
{name: 'VP',
related:[
{name: 'AVP',
related: []}
]}
]});
With this type of structure, myRole
will be all nested Role
classes.
NOTE: I will add a bit of caution, we have been using this structure in a production application for almost 4 years now (angular, typescript, redux), and I added the immutablejs for safety from mutated actions and stores. If I had to do it over, the strict immutable store and actions that comes with NGRX would be my choice. Immutablejs is great at what it does, but the complexity it adds to the app is a trade off (Especially for onboarding new/greener coders).
Upvotes: 1