mobxjs / mobx-state-tree

Full-featured reactive state management without the boilerplate
https://mobx-state-tree.js.org/
MIT License
6.94k stars 640 forks source link

getType ignore types.refinement #1369

Open swayf opened 5 years ago

swayf commented 5 years ago

Bug report

Sandbox link or minimal reproduction code

https://codesandbox.io/s/mobx-state-tree-todolist-52jt7

import { types, getType } from "mobx-state-tree";

const TestType = types.model("TestType", {
  test1: types.number,
  test2: types.number
});

const TestType2 = types.refinement(
  "TestType2",
  TestType,
  value => value.test1 + value.test2 === 100
);
const testModel2 = TestType2.create({ test1: 50, test2: 50 });

try {
  const testModel2Fail = TestType2.create({ test1: 50, test2: 40 });
} catch (e) {
  console.log("OK");
}

const test = getType(testModel2).create({ test1: 50, test2: 40 }); // should fail

Describe the expected behavior

last expression should fail (or?)

Describe the observed behavior

but it does not, because getType returns type before refinement

xaviergonz commented 5 years ago

That's because upon ".create", the refinement (validation) is run first, then it will instantiate that node as type "TestType", so getType will return that as type for that node. To fix this the refinement would need to be kept as type for the node (doable, but not easy).

xaviergonz commented 5 years ago

Not sure if a bug or an enhancement :)

eatyourgreens commented 1 year ago

We've found that you can use types.compose to change the name of the refined model, which doesn't help with dynamically creating new instances but does allow us to reflect on the type of a model created from a snapshot:

const TestType2 = types.refinement(
  types.compose("TestType2", TestType),
  value => value.test1 + value.test2 === 100
);
const testModel2 = TestType2.create({ test1: 50, test2: 50 });
getType(testModel2).name; // 'TestType2'

Using types.compose with just one model feels hacky, but that code does seem to work.