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()
, 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, 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.
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 anImmutable.Map
with structural sharing.