I have a React application that is backed by mobx-state-tree (MST). One of the pages makes two parallel API calls to fetch a list of Projects
and a list of system Users
.
The responses of these two APIs are then applied as snapshot to two sibling nodes in the same tree. The Project model's owner field refers to a User model.
My problem is that the GET project API call often completes earlier than the GET Users API call. Classic "race condition". So, when MST tries to dereference an owner value, the user with such identifier is not available in the tree and I'm getting a run time error like this:
Error: [mobx-state-tree] Failed to resolve reference 'dtGNbVdNOQIAX0OMGaH2r2oSEf2cMSk9AJzPAf4n7kA' to type 'User' (from node: /projectListStore/projects/0/owner)
How do I work around the error?
Please notice that making the API calls depend on each other (await
or users.load().then(users => { projects.load(); }
) are not an option for our project.
export const ProjectModel = types
.model('Project', {
projectId: types.optional(types.identifierNumber, 0),
title: '',
descriptionStatus: '',
projectStatus: '',
startDate: CustomDate,
endDate: CustomDate,
owner: types.maybeNull(types.reference(User)), // -----
}) \
// ... |
|
export const User = types |
.model('User', { /
userId: types.identifier, // <<<--
userName: '',
firstName: '',
lastName: '',
email: '',
lastVisited: CustomDate,
})
// ...
GET
https://service.com/api/users
[
{
"userId": "g-gqpB1EbTi9XGZOf3IkBpxSjpAHM8fpkeL4YZslEL8",
"userName": "[email protected]",
"firstName": "John",
"lastName": "Doe",
"email": "[email protected]",
"lastVisited": "2018-01-18T17:32:48.947"
},
{
...
},
...
]
GET
https://service.com/api/workmanagement/projects?minStartDate=&maxStartDate=2018-11-24&status=Active
[
{
"projectId": 1,
"title": "Project Title",
"description": "Praesent luctus vitae nunc sed condimentum. Duis maximus arcu tortor, vitae placerat enim ultrices sed. Etiam scelerisque gravida justo, vitae venenatis dui pellentesque eu.",
"projectStatus": "active",
"startDate": "2018-08-20T00:00:00",
"endDate": "2019-07-01T00:00:00",
"owner": "g-gqpB1EbTi9XGZOf3IkBpxSjpAHM8fpkeL4YZslEL8",
},
{
// ...
},
...
]
One solution you could apply is in the ProjectModel
model to define the owner as a string
owner: types.maybeNull(types.string),
Then define a computed property on the model that will retrieve the owner as an object
export const ProjectModel = types
.model('Project', {
projectId: types.optional(types.identifierNumber, 0),
title: '',
descriptionStatus: '',
projectStatus: '',
startDate: CustomDate,
endDate: CustomDate,
owner: types.maybeNull(types.reference(User)), // -----
}).views(self => ({
get ownerObject(): undefined | Instance<typeof User> {
// get the user manually from the tree
const root = getRoot(self)
return resolveIdentifier(User.Type, root, self.owner)
}
}))
You could use it like this project1.ownerObject ? project1.ownerObject.firstName : ''
The downside is that you will always need to check the ownerObject
exists whenever you are using it.
It sounds like you're potentially loading the user data after the project model is created It might be as easy as using a late reference. This is meant for, as it sounds, any types that may get created after this model is created.
instead of this
owner: types.maybeNull(types.reference(User))
try this
owner: types.maybeNull(types.late(() => types.reference(User)))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With