Normalization of nested data, a powerful concept which keeps you from pulling your hair out when multiple sources of truth and nested object hierarchies creep into your application
2. Data normalization
● A process that transforms denormalized data to normalized data
2
const article = {
id: 'article-1',
title: 'My Awesome Article',
author: {
id: 'user-1',
name: 'John'
}
};
3. Data normalization
● A process that transforms denormalized data to normalized data
3
const article = {
id: 'article-1',
title: 'My Awesome Article',
author: {
id: 'user-1',
name: 'John'
}
};
const entities = {
users: {
'user-1': {
id: 'user-1',
name: 'John'
}
},
articles: {
...
}
};
4. Data normalization
● A process that transforms denormalized data to normalized data
4
const article = {
id: 'article-1',
title: 'My Awesome Article',
author: {
id: 'user-1',
name: 'John'
}
};
const entities = {
users: {
'user-1': {
id: 'user-1',
name: 'John'
}
},
articles: {
'article-1': {
id: 'article-1',
title: 'My Awesome Article',
author: 'user-1'
}
}
};
5. Data normalization
● A process that transforms denormalized data to normalized data
5
const article = {
id: 'article-1',
title: 'My Awesome Article',
author: {
id: 'user-1',
name: 'John'
},
viewers: [{
id: 'user-2',
name: 'Alice'
}, {
id: 'user-1',
name: 'John'
}]
};
9. const article = {
id: 'article-1',
title: 'My Awesome Article',
author: {
id: 'user-1',
name: 'John'
},
viewers: [{
id: 'user-2',
name: 'Alice'
}, {
id: 'user-1',
name: 'John'
}]
};
Advantages
● Single source of truth
○ Denormalized data
■ name in multiple places
9
10. const entities = {
users: {
'user-1': {
id: 'user-1',
name: 'John'
},
'user-2': { ... }
},
articles: {
'article-1': {
id: 'article-1',
title: 'My Awesome Article',
author: 'user-1',
viewers: ['user-2', 'user-1']
}
};
Advantages
● Single source of truth
○ Denormalized data
■ name in multiple places
○ Normalized data
■ name in a single place
1
13. Disadvantage
● Difficult to read
○ We cannot use:
normalizedArticle.user.name
○ The user field is a string!
13
14. Disadvantage
● Difficult to read
○ We cannot use:
normalizedArticle.user.name
○ The user field is a string!
○ But we can denormalize the article object!
… you’ll see later.
14
15. Normalizr
● It is a library that converts denormalized data to normalized data
● It uses schema to describe relations between entities
● https://github.com/paularmstrong/normalizr
15
16. How to normalize data?
● Create schemas for your entities
● Call normalize()
16
17. Schema
import { schema } from 'normalizr';
const user = new schema.Entity('users');
const article = new schema.Entity('articles', {
author: user,
viewers: [user]
});
17
18. Schema
import { schema } from 'normalizr';
const user = new schema.Entity('users');
const comment = new schema.Entity('comments', {
commenter: user
});
const article = new schema.Entity('articles', {
author: user,
comments: [comment]
});
18
19. normalize() function
import { normalize } from 'normalizr';
const normalizedData = normalize(denormalizedData, schema);
○ denormalizedData - nested data (API response)
○ schema - normalizr schema
19
25. How to denormalize data?
● Use the same schemas
● Call denormalize()
○ denormalize(['article-1', 'article-2'], [article], entities);
25
26. How to denormalize data?
● Use the same schemas
● Call denormalize()
○ denormalize(['article-1', 'article-2'], [article], entities);
○ It denormalizes the whole data structure
○ It does not memoize previously normalized data
26
27. How to denormalize data?
● Use the same schemas
● Call denormalize()
○ denormalize(['article-1', 'article-2'], [article], entities);
○ It denormalizes the whole data structure
○ It does not memoize previously normalized data
● Use selectors!
27
28. Reselect
● A selector is a function that gets data from a state
○ const selector = state => state.name; // a simple selector without reselect
● Selectors can compute derived data from a state
○ They keep a single source of truth
● Selectors (with Reselect) are efficient (memoized)
○ They are not recomputed unless one of their arguments is changed
● Selectors (with Reselect) are composable
○ They can be used as input to other selectors
28
29. Reselect
● The selectors created with
createSelector are memoized
● The first arguments of
createSelector are other
selectors
● The last argument is a function which
has results of the previous selectors
in its input arguments
29
import { normalize } from 'normalizr';
const appState = {
counter: 0,
user: { name: 'John' }
};
const getCounter = state => state.counter;
const getUser = state => state.user;
const getName = createSelector(
getUser,
user => user.name
);
const getUpperCaseName = createSelector(
getUserName,
userName => userName.toUpperCase()
);
const getNameWithCounter = createSelector(
getUpperCaseName,
getCounter,
(userName, num) =,> `${userName} ${num}`
);