-
-
Save glenjamin/75a96b45f4bb5c6ac221815d28c548dd to your computer and use it in GitHub Desktop.
/* @flow */ | |
import * as I from "immutable"; | |
/** | |
* Define an immutable record intended for holding reducer state | |
* @param spec - the keys and their default values | |
* @return a state record factory function | |
*/ | |
export function defineRecord<T: Object>( | |
name: string, | |
spec: T | |
): (init: $Shape<T>) => Record<T> { | |
return I.Record(spec, name); | |
} | |
export type Record<T: Object> = RecordMethods<T> & T; | |
declare class RecordMethods<T: Object> { | |
get<A>(key: $Keys<T>): A; | |
set<A>(key: $Keys<T>, value: A): Record<T>; | |
update<A>(key: $Keys<T>, updater: (value: A) => A): Record<T>; | |
updateIn<A>(path: Iterable<any>, notSetOrUpdater: A | (value: A) => A, updater?: (value: A) => A): Record<T>; | |
setIn<A>(path: Iterable<any>, value: A): Record<T>; | |
deleteIn<A>(path: Iterable<any>): Record<T>; | |
merge(values: $Shape<T>): Record<T>; | |
inspect(): string; | |
toObject(): T; | |
// add more as needed | |
} |
@jedwards1211 - I like your approach, but it's a shame that you have to declare all the fields twice.
Also, but how do you organize your code? Do you put the RecordAPI
code somewhere likelib
, and import it? And do you create a folder for all the records, such as models
or records
? Or do you just define each Record inside the relevant reducer, and export it from there?
@glenjamin in your example, I'm struggling to understand how you use all of the things that are exported:
export type ThingShape
export type ThingRecord
export const Thing
const thing: ThingRecord = Thing({name: "blah"});
So in that example, thing
has a type of ThingRecord
, and so I guess Thing
is just a function that returns something with a type of ThingRecord
.
Ah, I think I get it. But I think it might be clearer if Thing
was renamed to createThingRecord
:
export const createThingRecord = defineRecord("Thing", ({
id: "",
name: "",
}: ThingShape));
Just to clarify that you're not creating a new Thing
class, you're calling a plain function that returns a ThingRecord
.
We've been able to get record flow types to work using the newest v4.0.0-RC-2 release and a bug fix to the record types. With these fixes we can do the following:
// create new record "class"
const newRecord = Record({ id: 0, name: '' });
// create a dummy instance of that record to use for typing
const dummyInst = newRecord();
// create the type to use when declaring the interface to a component
type recordInterface = typeof dummyInst;
// export the record "class" to be used for record instance creation
export { newRecord };
When a record instance is created and passed around, the type of the record is actually typeof dummyInst
not newRecord
. So in many cases, you create a dummy instance to generate the correct type, even though it may not be used anywhere in the actual javascript.
@ianwcarlson
I've tried to fork Immutable repository and apply mentioned bugfix. It doesn't seem to work.
const Person = Record({
name: null,
age: 0,
isAdult: false,
})
const personInstance = Person()
type TPerson = typeof personInstance
const Animal = Record({
name: null,
owner: null,
})
const animalInstance = Animal()
type TAnimal = typeof animalInstance
export const checkAge = (person: TPerson): void => {
if (person.age >= 18) {
console.log('ADULT')
} else {
console.log('CHILD')
}
}
export const foobar = () => {
const person = Person()
const animal = Animal()
checkAge(animal)
}
In this example, the Flow doesn't detect wrong type of object passed to checkAge
function. It doesn't event detect if I pass a native type there: checkAge(true)
.
Am I doing something wrong?
I'm also not able to get @ianwcarlson fix working. Has anyone been successful ?
It looks like one can use
$Subtype<T>
to make my first example compile:This now works:
I don't think my other problem can be fixed in Flow as it is now.