this is my first post here!
So as the context, let's say we talk about Backlog Items, Sprints & co. Let's say that we have an API that returns a list of Sprints and, for each Sprint, a list of Backlog Items committed to it:
[
{
id: 'sprint-1',
name: 'SPRINT 1',
backlog_items: [
{id: 'bli-1', name: 'BACKLOG 1'},
{id: 'bli-2', name: 'BACKLOG 2'},
],
},
{
id: 'sprint-2',
name: 'SPRINT 2',
backlog_items: [
{id: 'bli-3', name: 'BACKLOG 3'},
],
},
]
After normalisation (using normalizr library) I have:
{
entities: {
sprints: {
'sprint-1': {
id: 'sprint-1',
name: 'SPRINT 1',
backlogItems: ['bli-1', 'bli-2'],
},
'sprint-2': {
id: 'sprint-2',
name: 'SPRINT 2',
backlogItems: ['bli-3'],
},
},
backlogItems: {
'bli-1': {id: 'bli-1', name: 'BACKLOG 1', sprint: 'sprint-1'},
'bli-2': {id: 'bli-2', name: 'BACKLOG 2', sprint: 'sprint-1'},
'bli-3': {id: 'bli-3', name: 'BACKLOG 3', sprint: 'sprint-2'},
},
},
result: ['sprint-1', 'sprint-2'],
}
The entities part is stored inside Redux and a "Sprint list" UI correctly shows a list of items each with the Sprint name and the names of every Backlog Items assigned to that Sprint.
Now the user navigates to the "Sprint details" page, and an API call is used to fetch the latest version of that particular Sprint, let's say sprint-1. The response is:
{
id: 'sprint-1',
name: 'SPRINT 1',
backlog_items: [
{id: 'bli-1', name: 'BACKLOG 1'},
{id: 'bli-2', name: 'BACKLOG 2'},
{id: 'bli-4', name: 'BACKLOG 4'},
],
},
As we can see a new Backlog Item was committed to the Sprint, the normalised response is:
{
entities: {
sprints: {
'sprint-1': {
id: 'sprint-1',
name: 'SPRINT 1',
backlogItems: ['bli-1', 'bli-2', 'bli-4'],
},
},
backlogItems: {
'bli-1': {id: 'bli-1', name: 'BACKLOG 1', sprint: 'sprint-1'},
'bli-2': {id: 'bli-2', name: 'BACKLOG 2', sprint: 'sprint-1'},
'bli-4': {id: 'bli-4', name: 'BACKLOG 4', sprint: 'sprint-1'},
},
},
result: 'sprint-1',
}
This is merged with the previous state using a deep merge strategy, so that the final state is:
{
sprints: {
'sprint-1': {
id: 'sprint-1',
name: 'SPRINT 1',
backlogItems: ['bli-1', 'bli-2', 'bli-4'],
},
'sprint-2': {
id: 'sprint-2',
name: 'SPRINT 2',
backlogItems: ['bli-3'],
},
},
backlogItems: {
'bli-1': {id: 'bli-1', name: 'BACKLOG 1', sprint: 'sprint-1'},
'bli-2': {id: 'bli-2', name: 'BACKLOG 2', sprint: 'sprint-1'},
'bli-3': {id: 'bli-3', name: 'BACKLOG 3', sprint: 'sprint-2'},
'bli-4': {id: 'bli-4', name: 'BACKLOG 4', sprint: 'sprint-1'},
},
}
This is all fine, the "details page" show the correct information and even going back to the "list page" we have an updated UI thanks to the single source of truth state. Now we navigate to see the details of sprint-2, and we make a new API call:
{
id: 'sprint-2',
name: 'SPRINT 2',
backlog_items: [
{id: 'bli-3', name: 'BACKLOG 3'},
{id: 'bli-4', name: 'BACKLOG 4'},
],
},
Now we see that BACKLOG 4 was uncommitted from sprint-1 and committed to sprint-2. Again we perform normalisation and have:
{
entities: {
sprints: {
'sprint-2': {
id: 'sprint-2',
name: 'SPRINT 2',
backlogItems: ['bli-3', 'bli-4'],
},
},
backlogItems: {
'bli-3': {id: 'bli-3', name: 'BACKLOG 3', sprint: 'sprint-2'},
'bli-4': {id: 'bli-4', name: 'BACKLOG 4', sprint: 'sprint-2'},
},
},
result: 'sprint-2',
}
We deep merge this into our app state and suddenly we have:
{
sprints: {
'sprint-1': {
id: 'sprint-1',
name: 'SPRINT 1',
backlogItems: ['bli-1', 'bli-2', 'bli-4'],
},
'sprint-2': {
id: 'sprint-2',
name: 'SPRINT 2',
backlogItems: ['bli-3', 'bli-4'],
},
},
backlogItems: {
'bli-1': {id: 'bli-1', name: 'BACKLOG 1', sprint: 'sprint-1'},
'bli-2': {id: 'bli-2', name: 'BACKLOG 2', sprint: 'sprint-1'},
'bli-3': {id: 'bli-3', name: 'BACKLOG 3', sprint: 'sprint-2'},
'bli-4': {id: 'bli-4', name: 'BACKLOG 4', sprint: 'sprint-2'},
},
}
Now the state is inconsistent, because sprint-1 still thinks it has bli-4 committed to it even if bli-4 correctly reports that is is committed to the other sprint, sprint-2. The "list UI" is updated accordingly, showing an inconsistent view.
So the naïve deep merge approach is not good for this scenario, is it that inverse relations play not so good with normalised state or am I missing something?