Skip to content

Commit c300c79

Browse files
authored
fix typescript types after type optimization and cast, add castToSnapshot and castToReferenceSnapshot (#1074)
* fix ts types and cast, add castToSnapshot * make castToSnapshot accept instance or snapshot * test fix * fixed maybe and maybenull cases * fix for map put set * added some comments * lots of fixes, added castToReferenceSnapshot, made array ops not need cast * small tweak to test * api test fix * removed the now unecessary IComplexType
1 parent 27f56f7 commit c300c79

31 files changed

Lines changed: 1008 additions & 785 deletions

API.md

Lines changed: 497 additions & 416 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -924,10 +924,10 @@ Note that since MST v3 `types.array` and `types.map` are wrapped in `types.optio
924924
- `types.late(() => type)` can be used to create recursive or circular types, or types that are spread over files in such a way that circular dependencies between files would be an issue otherwise.
925925
- `types.frozen(subType? | defaultValue?)` Accepts any kind of serializable value (both primitive and complex), but assumes that the value itself is **immutable** and **serializable**.
926926
`frozen` can be invoked in a few different ways:
927-
- `types.frozen()` - behaves the same as types.frozen in MST 2.
928-
- `types.frozen(subType)` - provide a valid MST type and frozen will check if the provided data conforms the snapshot for that type. Note that the type will not actually be instantiated, so it can only be used to check the shape of the data. Adding views or actions to SubType would be pointless.
929-
- `types.frozen(someDefaultValue)` - provide a primitive value, object or array, and MST will infer the type from that object, and also make it the default value for the field
930-
- (Typescript) `types.frozen<TypeScriptType>(...)` - provide a typescript type, to help in strongly typing the field (design time only)
927+
- `types.frozen()` - behaves the same as types.frozen in MST 2.
928+
- `types.frozen(subType)` - provide a valid MST type and frozen will check if the provided data conforms the snapshot for that type. Note that the type will not actually be instantiated, so it can only be used to check the shape of the data. Adding views or actions to SubType would be pointless.
929+
- `types.frozen(someDefaultValue)` - provide a primitive value, object or array, and MST will infer the type from that object, and also make it the default value for the field
930+
- (Typescript) `types.frozen<TypeScriptType>(...)` - provide a typescript type, to help in strongly typing the field (design time only)
931931
- `types.compose(name?, type1...typeX)`, creates a new model type by taking a bunch of existing types and combining them into a new one.
932932

933933
## Property types
@@ -993,8 +993,10 @@ See the [full API docs](API.md) for more details.
993993
| [`addMiddleware(node, middleware: (actionDescription, next) => any, includeHooks)`](API.md#addmiddleware) | Attaches middleware to a node. See [middleware](docs/middleware.md). Returns disposer. |
994994
| [`applyAction(node, actionDescription)`](API.md#applyaction) | Replays an action on the targeted node |
995995
| [`applyPatch(node, jsonPatch)`](API.md#applypatch) | Applies a JSON patch, or array of patches, to a node in the tree |
996-
| [`cast(nodeOrSnapshot)`](API.md#cast) | Cast a node instance or snapshot to a node so it can be used in assignment operations |
997996
| [`applySnapshot(node, snapshot)`](API.md#applysnapshot) | Updates a node with the given snapshot |
997+
| [`cast(nodeOrSnapshot)`](API.md#cast) | Cast a node instance or snapshot to a node instance so it can be used in assignment operations |
998+
| [`castToSnapshot(nodeOrSnapshot)`](API.md#casttosnapshot) | Cast a node instance to a snapshot so it can be used inside create operations |
999+
| [`castToReferenceSnapshot(node)`](API.md#casttoreferencesnapshot) | Cast a node instance to a reference snapshot so it can be used inside create operations |
9981000
| [`createActionTrackingMiddleware`](API.md#createactiontrackingmiddleware) | Utility to make writing middleware that tracks async actions less cumbersome |
9991001
| [`clone(node, keepEnvironment?: true \| false \| newEnvironment)`](API.md#clone) | Creates a full clone of the given node. By default preserves the same environment |
10001002
| [`decorate(handler, function)`](API.md#decorate) | Attaches middleware to a specific action (or flow) |
@@ -1436,14 +1438,32 @@ s.replaceTasks([{ done: true }])
14361438
s.replaceTasks(types.array(Task).create([{ done: true }]))
14371439
```
14381440

1439-
Additionally, the `cast` function can be also used in the inverse case, this is when you want to use an instance inside an snapshot.
1440-
In this case MST will internally convert the instance to an snapshot before using it, but we need once more to fool TypeScript into
1441+
Additionally, the `castToSnapshot` function can be also used in the inverse case, this is when you want to use an instance inside an snapshot.
1442+
In this case MST will internally convert the instance to a snapshot before using it, but we need once more to fool TypeScript into
14411443
thinking that this instance is actually a snapshot.
14421444

14431445
```typescript
14441446
const task = Task.create({ done: true })
1447+
const Store = types.model({
1448+
tasks: types.array(Task)
1449+
})
1450+
14451451
// we cast the task instance to a snapshot so it can be used as part of another snapshot without typing errors
1446-
const s = Store.create({ tasks: [cast(task)] })
1452+
const s = Store.create({ tasks: [castToSnapshot(task)] })
1453+
```
1454+
1455+
Finally, the `castToReferenceSnapshot` can be used when we want to use an instance to actually use a reference snapshot (a string or number).
1456+
In this case MST will internally convert the instance to a reference snapshot before using it, but we need once more to fool TypeScript into
1457+
thinking that this instance is actually a snapshot of a reference.
1458+
1459+
```typescript
1460+
const task = Task.create({ id: types.identifier, done: true })
1461+
const Store = types.model({
1462+
tasks: types.array(types.reference(Task))
1463+
})
1464+
1465+
// we cast the task instance to a reference snapshot so it can be used as part of another snapshot without typing errors
1466+
const s = Store.create({ tasks: [castToReferenceSnapshot(task)] })
14471467
```
14481468

14491469
#### Known Typescript Issue 5938

changelog.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# 3.8.0
2+
3+
- Added castToSnapshot/castToReferenceSnapshot methods for TypeScript and fixed some TypeScript typings not being properly detected when using SnapshotIn types through [#1074](https://github.com/mobxjs/mobx-state-tree/pull/1074) by [@xaviergonz](https://github.com/xaviergonz)
14
- Fixed redux middleware throwing an error when a flow is called before it is connected [#1065](https://github.com/mobxjs/mobx-state-tree/issues/1065) through [#1079](https://github.com/mobxjs/mobx-state-tree/pull/1079) by [@mkramb](https://github.com/mkramb) and [@xaviergonz](https://github.com/xaviergonz)
25
- Made `addDisposer` return the passed disposer through [#1059](https://github.com/mobxjs/mobx-state-tree/pull/1059) by [@xaviergonz](https://github.com/xaviergonz)
36

packages/mobx-state-tree/__tests__/core/action.test.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import {
99
getRoot,
1010
cast,
1111
IMiddlewareEvent,
12-
ISerializedActionCall
12+
ISerializedActionCall,
13+
Instance
1314
} from "../../src"
1415

1516
/// Simple action replay and invocation
@@ -93,10 +94,10 @@ const Order = types
9394
customer: types.maybeNull(types.reference(Customer))
9495
})
9596
.actions(self => {
96-
function setCustomer(customer: typeof Customer.Type) {
97+
function setCustomer(customer: Instance<typeof Customer>) {
9798
self.customer = customer
9899
}
99-
function noopSetCustomer(_: typeof Customer.Type) {
100+
function noopSetCustomer(_: Instance<typeof Customer>) {
100101
// noop
101102
}
102103
return {
@@ -390,7 +391,7 @@ test("after attach action should work correctly", () => {
390391
todos: types.array(Todo)
391392
})
392393
.actions(self => ({
393-
remove(todo: typeof Todo.Type) {
394+
remove(todo: Instance<typeof Todo>) {
394395
self.todos.remove(todo)
395396
}
396397
}))

packages/mobx-state-tree/__tests__/core/api.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ const METHODS_AND_INTERNAL_TYPES = stringToArray(`
5555
getMembers,
5656
getPropertyMembers,
5757
cast,
58+
castToSnapshot,
59+
castToReferenceSnapshot,
5860
isType,
5961
isArrayType,
6062
isFrozenType,

packages/mobx-state-tree/__tests__/core/array.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ test("it should create a factory", () => {
3232
})
3333
test("it should succeed if not optional and no default provided", () => {
3434
const Factory = types.array(types.string)
35-
expect(Factory.create().toJSON!()).toEqual([])
35+
expect(getSnapshot(Factory.create())).toEqual([])
3636
})
3737
test("it should restore the state from the snapshot", () => {
3838
const { Factory } = createTestFactories()
@@ -147,13 +147,13 @@ test("paths shoud remain correct when splicing", () => {
147147
})
148148
unprotect(store)
149149
expect(store.todos.map(getPath)).toEqual(["/todos/0"])
150-
store.todos.push(cast({}))
150+
store.todos.push({})
151151
expect(store.todos.map(getPath)).toEqual(["/todos/0", "/todos/1"])
152-
store.todos.unshift(cast({}))
152+
store.todos.unshift({})
153153
expect(store.todos.map(getPath)).toEqual(["/todos/0", "/todos/1", "/todos/2"])
154154
store.todos.splice(0, 2)
155155
expect(store.todos.map(getPath)).toEqual(["/todos/0"])
156-
store.todos.splice(0, 1, cast({}), cast({}), cast({}))
156+
store.todos.splice(0, 1, {}, {}, {})
157157
expect(store.todos.map(getPath)).toEqual(["/todos/0", "/todos/1", "/todos/2"])
158158
store.todos.remove(store.todos[1])
159159
expect(store.todos.map(getPath)).toEqual(["/todos/0", "/todos/1"])

packages/mobx-state-tree/__tests__/core/boxes-store.test.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,15 @@
22
* Based on examples/boxes/domain-state.js
33
*/
44
import { values } from "mobx"
5-
import { types, getParent, hasParent, recordPatches, unprotect, getSnapshot } from "../../src"
5+
import {
6+
types,
7+
getParent,
8+
hasParent,
9+
recordPatches,
10+
unprotect,
11+
getSnapshot,
12+
Instance
13+
} from "../../src"
614

715
export const Box = types
816
.model("Box", {
@@ -56,15 +64,15 @@ export const Store = types
5664
function addArrow(id: string, from: string, to: string) {
5765
self.arrows.push(Arrow.create({ id, from, to }))
5866
}
59-
function setSelection(selection: typeof Box.Type) {
67+
function setSelection(selection: Instance<typeof Box>) {
6068
self.selection = selection
6169
}
6270
function createBox(
6371
id: string,
6472
name: string,
6573
x: number,
6674
y: number,
67-
source: typeof Box.Type | null | undefined,
75+
source: Instance<typeof Box> | null | undefined,
6876
arrowId: string | null
6977
) {
7078
const box = addBox(id, name, x, y)

packages/mobx-state-tree/__tests__/core/custom-type.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ class Decimal {
8383
const b1 = w1.balance
8484
expect(b1).toBeInstanceOf(Decimal)
8585

86-
w1.balance = cast("2.5")
86+
w1.balance = "2.5" as any // TODO: make cast work with custom types
8787
expect(b1).toBeInstanceOf(Decimal)
8888
expect(w1.balance).toBe(b1) // reconciled
8989

@@ -93,7 +93,7 @@ class Decimal {
9393
w1.balance = new Decimal("3.5")
9494
expect(b1).toBeInstanceOf(Decimal)
9595

96-
w1.balance = cast("4.5")
96+
w1.balance = "4.5" as any
9797
expect(b1).toBeInstanceOf(Decimal)
9898

9999
w1.lastTransaction = b1
@@ -163,7 +163,7 @@ class Decimal {
163163
const b1 = w1.balance
164164
expect(b1).toBeInstanceOf(Decimal)
165165

166-
w1.balance = cast([2, 5])
166+
w1.balance = [2, 5] as any
167167
expect(b1).toBeInstanceOf(Decimal)
168168
expect(w1.balance).not.toBe(b1) // not reconciled, balance is not deep equaled (TODO: future feature?)
169169

@@ -173,7 +173,7 @@ class Decimal {
173173
w1.balance = new Decimal("3.5")
174174
expect(b1).toBeInstanceOf(Decimal)
175175

176-
w1.balance = cast([4, 5])
176+
w1.balance = [4, 5] as any
177177
expect(b1).toBeInstanceOf(Decimal)
178178

179179
// patches & snapshots

packages/mobx-state-tree/__tests__/core/identifier.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ if (process.env.NODE_ENV !== "production") {
3131
})
3232
.actions(self => ({
3333
addModel(model: SnapshotOrInstance<typeof Model>) {
34-
self.models.push(cast(model))
34+
self.models.push(model)
3535
}
3636
}))
3737
expect(() => {

packages/mobx-state-tree/__tests__/core/jsonpatch.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ test("it should apply deep patches to objects", () => {
288288
NodeObject,
289289
{ id: 1, child: { id: 2 } },
290290
(n: Instance<typeof NodeObject>) => {
291-
n.child.text = "test" // update
291+
n.child!.text = "test" // update
292292
n.child = cast({ id: 2, text: "world" }) // this reconciles; just an update
293293
n.child = NodeObject.create({ id: 2, text: "coffee", child: { id: 23 } })
294294
n.child = cast({ id: 3, text: "world", child: { id: 7 } }) // addition

0 commit comments

Comments
 (0)