Populate allows you to replace IDs within your data with other data from Firebase. This is very useful when trying to keep your data flat. Some would call it a join, but it was modeled after the mongo populate method.
Initial data from populate is placed into redux in a normalized pattern following defined redux practice of normalizing. populate
helper used within the connect
function then builds populated data out of normalized data within redux (NOTE: This does not apply if you are using v1.1.5
or earlier).
A basic implementation can look like so:
import { compose } from 'redux'
import { connect } from 'react-redux'
import { firebaseConnect, populate } from 'react-redux-firebase'
const populates = [
{ child: 'owner', root: 'users' } // replace owner with user object
]
const enhance = compose(
firebaseConnect([
// passing populates parameter also creates all necessary child queries
{ path: 'todos', populates }
]),
connect(({ firebase }) => ({
// populate original from data within separate paths redux
todos: populate(firebase, 'todos', populates),
// firebase.ordered.todos or firebase.data.todos for unpopulated todos
}))
)
export default enhance(SomeComponent)
- Population happens in two parts:
firebaseConnect
- based on populate settings, queries are created for associated keys to be replaced. Query results are stored in redux under the value ofroot
in the populate settings.connect
- Combine original data at path with all populate data (in redux from queries created by passing populate settings tofirebaseConnect
)
- Populate creates a query for each key that is being "populated", but does not create multiple queries for the same key
- Results of populate queries are placed under their root
List of todo items where todo item can contain an owner parameter which is a user's UID like so:
{ "text": "Some Todo Item", "owner": "Iq5b0qK2NtgggT6U3bU6iZRGyma2" }
Populate allows you to replace the owner parameter with another value on Firebase under that key. That value can be a string (number and boolean treated as string), or an object
Initial data from populate is placed into redux in a normalized pattern following defined redux practice of normalizing. populatedDataToJS
helper used in the connect
function then builds populated data out of normalized data within redux (NOTE: This does not apply if you are using v1.1.5
or earlier).
"todos": {
"ASDF123": {
"text": "Some Todo Item",
"owner": "Iq5b0qK2NtgggT6U3bU6iZRGyma2"
}
},
"displayNames": {
"Iq5b0qK2NtgggT6U3bU6iZRGyma2": "Morty Smith",
"6Ra53mf3U9Qmdwah6rXBMgY8smu1": "Rick Sanchez"
}
"users": {
"Iq5b0qK2NtgggT6U3bU6iZRGyma2": {
"displayName": "Morty Smith",
"email": "[email protected]"
},
"6Ra53mf3U9Qmdwah6rXBMgY8smu1": {
"displayName": "Rick Sanchez",
"email": "[email protected]"
}
}
When trying to replace the owner parameter with a string such as a displayName from a /displayNames
root follow the pattern of #populate=paramToPopulate:populateRoot
.
const populates = [
{ child: 'owner', root: 'displayNames' }
]
const enhance = compose(
firebaseConnect([
{ path: '/todos', populates }
// '/todos#populate=owner:displayNames', // equivalent string notation
]),
connect(
({ firebase }) => ({
todos: populate(firebase, 'todos', populates),
})
)
)
ASDF123: {
text: 'Some Todo Item',
owner: 'Morty Smith'
}
Population can also be used to populate a parameter with an object. An example of this would be populating the owner
parameter, which is an ID, with the matching key from the users
list.
const populates = [
{ child: 'owner', root: 'users' }
]
const enhance = compose(
firebaseConnect([
{ path: '/todos', populates }
// '/todos#populate=owner:users' // equivalent string notation
]),
connect(
({ firebase }) => ({
todos: populate(firebase, 'todos', populates),
})
)
)
ASDF123: {
text: 'Some Todo Item',
owner: {
displayName: 'Morty Smith',
email: '[email protected]'
}
}
Often when populating, you will want to keep the key that was originally there (before being replaced by populated value). This is supported through the use of keyProp
:
NOTE: Similar results also be accomplished using childAlias
since child (key in this case) will be preserved if populate result is "aliased"
const populates = [
{ child: 'owner', root: 'users', keyProp: 'key' }
]
const enhance = compose(
firebaseConnect([
{ path: '/todos', populates }
]),
connect(
({ firebase }) => ({
todos: populate(firebase, 'todos', populates),
})
)
)
ASDF123: {
text: 'Some Todo Item',
owner: {
key: 'Iq5b0qK2NtgggT6U3bU6iZRGyma2',
displayName: 'Scott Prue',
email: '[email protected]'
}
}
There is also the option to place the results of a populate on another parameter instead of replacing the original child (i.e. "alias" the child result). An example of this could be populating the owner
parameter onto the ownerObj
parameter, which would leave the owner
parameter intact (since the child from the populate was "aliased" to ownerObj
).
For more details including the initial feature request, checkout issue #126.
const populates = [
{ child: 'owner', root: 'users', childAlias: 'ownerObj' }
]
const enhance = compose(
firebaseConnect([
{ path: '/todos', populates }
]),
connect(
({ firebase }) => ({
todos: populate(firebase, 'todos', populates),
})
)
)
ASDF123: {
text: 'Some Todo Item',
owner: "Iq5b0qK2NtgggT6U3bU6iZRGyma2",
ownerObj: {
displayName: "Morty Smith",
email: '[email protected]',
}
}
There is also the option to load a parameter from within a population object. An example of this could be populating the owner
parameter with the email
property of the user
with a matching ID:
const populates = [
{ child: 'owner', root: 'users', childParam: 'email' }
]
const enhance = compose(
firebaseConnect([
{ path: '/todos', populates }
// '/todos#populate=owner:users:email' // equivalent string notation
]),
connect(
({ firebase }) => ({
todos: populate(firebase, 'todos', populates),
})
)
)
ASDF123: {
text: 'Some Todo Item',
owner: '[email protected]'
}
To Populate parameters within profile/user object, include the profileParamsToPopulate
parameter within your react-redux-firebase config.
Populating username with username from usernames ref.
const config = {
userProfile: 'users',
profileParamsToPopulate: [
{ child: 'displayName', root: 'displayNames' } // object notation
]
}
{
users: {
$uid: {
email: '[email protected]',
displayName: 'Iq5b0qK2NtgggT6U3bU6iZRGyma2'
}
}
}
{
users: {
$uid: {
email: '[email protected]',
displayName: 'someuser'
}
}
}
const config = {
userProfile: 'users',
profileParamsToPopulate: [ 'todos:todos' ] // populate list of todos from todos ref
}
{
users: {
$uid: {
email: '[email protected]',
todos: {
0: "ASDF123"
}
}
}
}
{
users: {
$uid: {
email: '[email protected]',
todos: {
ASDF123: {
text: 'Some Todo Item',
owner: "Iq5b0qK2NtgggT6U3bU6iZRGyma2"
}
}
}
},
todos: {
ASDF123: {
text: 'Some Todo Item',
owner: "Iq5b0qK2NtgggT6U3bU6iZRGyma2"
}
}
}
To use populate with Firestore, just replace firebaseConnect with firestoreConnect with the corresponding charaters.
import { firestoreConnect, populate } from 'react-redux-firebase';
const populates = [
{ child: 'owner', root: 'users', childAlias: 'ownerObj' }
]
const enhance = compose(
firestoreConnect([
{ collection: 'todos', populates }
]),
connect(
({ firebase }) => ({
todos: populate(firebase, 'todos', populates),
})
)
)