firebase / firebase/firebase-js-sdk
FR: Provide way to leverage structural sharing in DataSnapshot
- Dominant language
- TypeScript
- Stars
- 5.1k
- Forks
- 1k
- Avg merge
- 2d 21h
- Merged PRs (30d)
- 37
Description
### [REQUIRED] Describe your environment
* Operating System version: Mac OS X 10.12
* Firebase SDK version: 4.1.1
* Firebase Product: database
### [REQUIRED] Describe the problem
In React-based apps, we optimize the performance of the app with “structural sharing.”
Since a DataSnapshot is immutable, I would expect a `DataSnapshot` of an unchanged child to be the same object in previous snapshot.
#### Steps to reproduce:
Consider the state of the realtime database where `task2.title` changed:
```
// s1 and s2 are `DataSnapshot` objects representing a snapshot of at same `Reference` at different point in time.
s1 -> { task1: { title: 'A' }, task2: { title: 'B' } }
s2 -> { task1: { title: 'A' }, task2: { title: 'Z' } }
```
Since between these 2 snapshots, the data of `task1` child is unchanged. Therefore, I would expect that:
```
s1.child('task1') === s2.child('task1')
```
However, a [new DataSnapshot is created every time you call `.child()`](https://github.com/firebase/firebase-js-sdk/blob/3f827b82aa83cb6b5538bb7881f3253e9af0d15f/packages/database/src/api/DataSnapshot.ts#L87-L100), hence, the above code yields `false`.
A workaround is to compare the underlying node:
```
s1.child('task1').node_ === s2.child('task1').node_
```
But it does not work with the hosted Firebase SDK as it gets closure-compiled, and `node_` was renamed to `A`.
Having structural sharing makes it much easier to [optimize a React application](https://reactjs.org/docs/optimizing-performance.html#avoid-reconciliation), by leveraging `PureComponent` and `shouldComponentUpdate`.
#### Relevant Code:
The workaround I am currently using is to parse the source code of `dataSnapshot.val()` at runtime to obtain access to the underlying node object.
```javascript
function getUnderlyingNode (dataSnapshot) {
// ULITAME HACK:
// PARSE THE FIREBASE SDK SOURCE CODE TO FIGURE OUT HOW
// TO OBTAIN THE UNDERLYING NODE.
const match = dataSnapshot.val.toString().match(/return\s+this\.(\w+)/)
return dataSnapshot[match[1]]
}
```
By comparing the underlying `node_`, I was able to improve the performance of my React app by ~100x. (It works with a lot of data.)
#### API suggestions:
To avoid such ugly hack, here are some ways that the DataSnapshot API can be improved so that userland code could leverage structural sharing:
- Providing access to the underlying node. This node should be treated as an opaque object, but can be used as, e.g., a cache key for `WeakMap` to perform snapshot memoization.
- Providing a method, e.g. `dataSnapshot.frozenVal()` that returns a frozen object/array with structural sharing.
- Providing a method, e.g. `dataSnapshot.immutableVal()` that returns an `Immutable.Map` with structural sharing.
Contributor guide
Assessment
This issue has not been assessed yet.