React-redux-firebase: How to get data by user id?

Created on 27 Nov 2016  路  30Comments  路  Source: prescottprue/react-redux-firebase

Hi!

My database store data like this

/root
    /sheets
        /$user_id
            /some_data

I try to get data from Firebase by current user id. With this solution my app make tons of requests to Firebase.

const wrappedComponent = firebase((params,auth) => {
  let user = auth.auth().currentUser
  return ['/sheets/' + (user!=null?user.uid:"")]
} )(Container)

export default connect(
  ({firebase}) => ({
    sheets: dataToJS(firebase, 'sheets'),
    account: pathToJS(firebase, 'profile')
  })
)(wrappedComponent)

I think I do something wrong :) Can you help me?

question

Most helpful comment

@Barath-prakash excellent. @prescottprue would you be open to a PR for a "data by user ID example"? Love what you've got here!

All 30 comments

This could be done by using a redux connect call before your firebase connect:

const authConnected = connect(
 ({ firebase }) => ({
    auth: pathToJS(firebase, 'auth') // gets auth from redux and sets as prop
  })
)(Container)

const firebaseConnected = firebaseConnect(
  ({ auth }) => ([
    // Get auth from props
    auth ? `/sheets/${auth.uid}` : '/sheets'
  ])
)(authConnected)

export default connect(
 ({ firebase }, { auth }) => ({
    // pathToJS(firebase, 'auth') gets from redux, but auth is already a prop
    sheets: dataToJS(firebase, auth ? `/sheets/${auth.uid}` : '/sheets'),
 })
)(firebaseConnected)

Planning on leaving this issue open until I add an example of this.

Props With Children

If you have a parent with children that has already gotten auth as a prop, you can pass it down as a prop to children using React.cloneElement. Here is a snippet from a sidebar (full example to come):

export const SidebarLayout = ({ children, auth }) => (
  <div>
    <Sidebar />
    <div>
      { React.cloneElement(children, { auth }) }
    </div>
  </div>
);

Also planning on including this in one of the examples.

Hi! Thank you for answers. Now I can get user id for request :)

But I see another problem, app render Container component every second :( Sorry for tons of questions

@makadaw Do you want to hop on the gitter channel? Might be a little bit quicker.

Sorry for a beginner question, but in your SidebarLayout example:

  1. How come auth is not managed by Redux? What are the guidelines to make that decision?
  2. Why are you not using Context for sending arguments to grand-children? NOTE: That is also how react-redux implements the Provider component.

Auth is "managed by" redux as in the state of auth is within redux state.auth (not sure what you mean by "managed by"). The actual authentication network communication is handled by Firebase.

This is a wrapper that gets auth from redux and provides it as a prop, then the firebaseConnect uses that auth to make a query (the result of which is mapped to a prop within the next connect).

Currently this is setup so that you use react-redux's connect function to pass mapStateToProps. We could use context, but then everything from store would have to be passed as a prop. With the current setup, you choose what of your Firebase data is available in your component based on how you write your mapStateToProps. Does that answer your question?

Feel free to reach out on the gitter channel if you still have an questions or have ideas for another pattern.

Hey @prescottprue did you post any live example to get data from /users/${auth.uid}/path?

@cropcrop There is access to your user profile through pathToJS(state.firebase, 'profile') if you have set the userProfile config.

So for you, you would want something like:

import React, { Component, PropTypes } from 'react';
import { firebaseConnect, pathToJS } from 'react-redux-firebase';
import { connect } from 'react-redux';

@firebaseConnect()
@connect(
 ({ firebase }) => ({
    // gets profile from redux and passes as prop
    profile: pathToJS(firebase, 'profile'),
 })
)
export default class SomeComponent extends Component {
  render() {
    // this.props.profile.email === /users/${auth.uid}/email
  }
}

Feel free to open an issue if this does not answer your question.

Great, thanks @prescottprue ;)

The example you provided earlier would default to loading all user recipes if the auth.uid wasn't found. Won't this be a problem if I have loads of users? How do I load from just /recipes/${auth.uid}?

Wondering if I'm missing something...

I tried something like this but it would throw Cannot read property 'uid' of undefined

const authConnected = connect(
  ({ firebase }) => ({
    auth: pathToJS(firebase, 'auth') // gets auth from redux and sets as prop
  })
)(RecipePageContainer)

const firebaseConnected = firebaseConnect(
  ({ auth }) => ([
    // Get auth from props
    `/recipes/${auth.uid}`
  ])
)(authConnected)

export default connect(
 ({ firebase }, { auth }) => ({
    // pathToJS(firebase, 'auth') gets from redux, but auth is already a prop
    recipes: dataToJS(firebase, `/recipes/${auth.uid}`)
 })
)(firebaseConnected)

You need to make sure that auth is already defined as a prop (which it will not be on initial load because it takes time).

This can be done by waiting for auth to be loaded in a parent component, then loading a child component with that prop passed:

const authConnected = connect(
  ({ firebase }) => ({
    auth: pathToJS(firebase, 'auth') // gets auth from redux and sets as prop
  })
)(SomeParent)

class SomeParent extends Component {
  render () {
    if (!isLoaded(this.props.auth)) {
      return <div>Loading...</div>
    }
    return <RecipesPage auth={this.props.auth} />
  }
}

Then your child component will only be rendered if auth is available:

const firebaseConnected = firebaseConnect(
  ({ auth }) => ([
    // Get auth from props
    `/recipes/${auth.uid}`
  ])
)(RecipesPage)

export default connect(
 ({ firebase }, { auth }) => ({
    // pathToJS(firebase, 'auth') gets from redux, but auth is already a prop
    recipes: dataToJS(firebase, `/recipes/${auth.uid}`)
 })
)(RecipesPage)

Note: Higher order component wrapping like this is much cleaner when using es7 decorator syntax

It is understood that this is a common pattern for developers and I am working on a way to simplify running this logic (open to input).

Currently, the advanced routing example uses redux-auth-wrapper, which makes logic like this easy to write and assign to routes/components.

Another Pattern (using populate)

If the "recipes" or other objects are associated with the user, you could store a list of keys on the user under a parameter such as "recipes", then populate it:
user object

{
  displayName: 'Some Guy',
  recipes: {
    "123SOMEKEY": true,
    "234OTHERKEY": true
  }
}

recipes collection

{
  recipes: {
    "123SOMEKEY": {
       instructions: 'whip it',
       ingredients: 'eggs, butter'
    },
    "234OTHERKEY": {
       instructions: 'melt it',
        ingredients: 'cheese'
     }
  }
}

populating current logged in user
Using profileParamsToPopulate as described in the docs

// config passed into reactReduxFirebase when creating store
{
  userProfile: 'users',
  profileParamsToPopulate: ['recipes:recipes'] // populate recipes child from recipes root collection
}

@prescottprue Thanks for this, I'll implement your first example with ES6 decorators :)

Firebase........ how do i get login persons uid into the another controller... @prescottprue

@Barath-prakash There are no "controllers" in React, this issue, or any of the provided solutions. You are most likely using Angular, which is not yet supported for react-redux-firebase.

If you are having an issue implementing react-redux-firebase, please open a issue with relevant information or reach out over gitter.

Yes am using angular ionic with firebase...... okay So Where will i get solution for my issue....????? @prescottprue

@Barath-prakash try Stack Overflow

i found solution @karltaylor

@Barath-prakash excellent. @prescottprue would you be open to a PR for a "data by user ID example"? Love what you've got here!

I need to be able to perform this same "data by user ID" as well and I can't seem to get it working. My current code leaves me with this error: "In this environment the sources for assign MUST be an object.This error is a performance optimization and not spec compliant."

@connect(({ firebase: { auth } }) => ({ auth }))

@firebaseConnect(({ auth }) => [userdata/${auth.uid}/trackerdata])

@connect( ({ firebase: { data}) => ({ data }))

export default class MainScreen extends React.Component {
///MainScreen Code
}

@csumrell If you are attempting to run a query based on auth state, checkout the state based query example (which is updated based on early comments in this issue).

If it is still not working as expected, please open a new issue with a full example listing which version you are using.

@prescottprue I'm using version 2.0, based on the earlier code snippet you posted https://github.com/prescottprue/react-redux-firebase/issues/14#issuecomment-303259497. I have tried (the first approach) to access the uid inside @firebaseConnect decorator but the state seems to be an empty object, hence firebase is undefined.

@firebaseConnect(state => {
  console.log(state) // -> {}
  const uid = state.firebase.auth.uid || 'demo'
  return [`/resouces/${uid}/resouceId`] // I want the current user's uid available here
})
@connect(mapStateToProps)

I have tried the second approach a half year ago and experienced some performance issue, for example, fetching a user's info which will also fetch all the sub-data under the user.

@li-xinyang The first argument that firebaseConnect recieves is props not state. That means it will be an empty object unless you pass props.

If you need uid in firebaseConnect and are not passing it as a prop you can do the following:

@firebaseConnect((props, firebase) => {
  console.log(props) // -> {} if you don't pass any props
  const uid = firebase._.authUid || 'demo'
  return [`/resouces/${uid}/resouceId`] // I want the current user's uid available here
})
@connect(mapStateToProps)

Or you can pass auth as a prop from a parent component as is done in state based query example

It's great being able to access authUid like this, without needing to @connect first just to get auth from the store. I'm scared of firebase._ though! _ is clearly an internal object not intended to be accessed by users. Maybe you could make it available at firebase.authUid too?

@jake-nz Yes it is an internal object. That is an interesting suggestion, going to look into it a bit more and potentially add something to the roadmap.

I cannot retreive the data when I use auth.uid to retrieve the data. Here is how my data structure looks:

Firebase Datastructure

The data loads fine when i try to access all the data of the playlist collection:

export default compose(
    firebaseConnect((props, firebase) => [`/playlists/`] ),
    connect(
      (state, props) => ({
        playlists: state.firebase.data.playlists,
      })
    )
  )(PlayListGroup);

I get all the data of the playlists collection
loads all data without the auth.props

But If I try this, to get data from this route: /playlists/jze7uyWtpAXmqy0085G6h8In8vI2/ I get null:

export default compose(
    //firebaseConnect((props, firebase) => [`/playlists/`] ),
    //here props.auth = returns the current user id: jze7uyWtpAXmqy0085G6h8In8vI2 
    firebaseConnect((props, firebase) => [`/playlists/${props.auth}/`] ),
    connect(
      (state, props) => ({
        playlists: state.firebase.data.playlists,
      })
    )
  )(PlayListGroup);

I get this:
But get null with the auth.props


But the data loads fine from that route fine when I fetch it with firebase.ref:

componentDidMount(){
    this.props.firebase.ref('playlists/2Fjze7uyWtpAXmqy0085G6h8In8vI2').on("value", function(snapshot) {
      console.log(snapshot.val());
    }, function (errorObject) {
      console.log("The read failed: " + errorObject.code);
    });
}

The rules are set to public read and write since I am still developing. What am I doing wrong, can you please shed some light?

Thanks

Update:

Looks like if I try to hardcode the route, it loads fine:

export default compose(
    //firebaseConnect((props, firebase) => [`/playlists/`] ),
    //here props.auth = returns the current user id: jze7uyWtpAXmqy0085G6h8In8vI2 
    firebaseConnect((props, firebase) => [`/playlists/jze7uyWtpAXmqy0085G6h8In8vI2/`] ),
    connect(
      (state, props) => ({
        playlists: state.firebase.data.playlists,
      })
    )
  )(PlayListGroup);

But I am passing the props like this, so the auth prop should not be empty:

{isLoaded(this.props.profile) ? <PlayListGroup auth={this.props.firebase._.authUid} />:<p>loading...</p> }

Tried this, as suggested in the state based query example, and got the same null data:

 firebaseConnect((props, store) =>  {
        const { firebase: { auth } } = store.getState()
        // be careful, listeners are not re-attached when auth state changes unless props change
        return [{ path: `playlists/${auth.uid || ''}` }]
 }),

@towfiqi As noted in the README of the state based query example, using store.getState() can have unintended consequences. It is best to get auth from state and pass it as a prop using connect - from their you can wait for it to load first (as noted in that example as well as in the query docs).

Also something to note (which it looks like is not handled in your examples) is that data is placed into redux under its path by default (in your case playlists/${auth.uid}) which means you need to retrieve them that way or use storeAs i.e.:

import { getVal } from 'react-redux-firebase' // lodash's get can be used for dot notation

export default compose(
  firebaseConnect((props, firebase) => [
    `/playlists/jze7uyWtpAXmqy0085G6h8In8vI2/`
  ]),
  connect((state, props) => ({
    // playlists: state.firebase.data.playlists, // will not work unless you use storeAs: 'playlists'
    playlists: getVal(state.firebase, `data/playlists/${auth.uid}`) // notice that path includes uid
  }))
)(PlayListGroup);

or using storeAs:

export default compose(
  firebaseConnect((props, firebase) => [
    {
      path: `/playlists/jze7uyWtpAXmqy0085G6h8In8vI2/`,
      storeAs: 'playlists'
    }
  ]),
  connect((state, props) => ({
    playlists: state.firebase.data.playlists, // works because of storeAs
  }))
)(PlayListGroup);

@prescottprue Thanks for your quick response. I have gone through the links you shared lots of times, before posting and tried all the methods posted in the examples and guides. :(

I am already loading the auth as a prop in PlayListGroup and tried both your suggested ways,

class Home extends React.Component {
  render(){
    return(
      <div id="now_playing">
        //Passing the auth props here:
        <PlayListGroup auth={isEmpty(this.props.auth) === false && this.props.auth} />
        <PlayList />
        <Player />
      </div>
    );
  }
}
export default compose(
  withFirebase,
  connect(
    (state, props) => ({
      //playlists: state.firebase.data.playlists,
      userSettings: state.user,
      auth: state.firebase.auth,
      profile: state.firebase.profile // load profile
    })
  )
)(Home);

but I still get playlists: {jze7uyWtpAXmqy0085G6h8In8vI2: null}

But get null with the auth.props

I am checking if the auth props that I passed in PlayListGroup Exist and it does and also I can console.log it and get the value, but its not working:

export default compose(
    firebaseConnect((props, firebase) => { 
        console.log('firebaseconnect props: ',props); //logs the props.auth fine.
        return props.auth.uid ? [`/playlists/${props.auth.uid}/`] : [`playlists`];  <----Doesnt Works
        //return props.auth.uid ? [`/playlists/jze7uyWtpAXmqy0085G6h8In8vI2/`] : [`playlists`];  <----Works
    }),
    connect(
      (state, props) => ({
        playlists: state.firebase.data.playlists,
      })
    )
  )(PlayListGroup);

Above code returns null, but If I replace the ${props.auth} with hardcoded jze7uyWtpAXmqy0085G6h8In8vI2 it loads the playlist fine.

From the looks of it from your connect it seems that the whole auth object is being passed as a prop instead of just the auth uid (which makes the picture you posted a little weird). You want props.auth.uid not props.auth if you have connected straight from staate. Note that the listener should be like so:

firebaseConnect((props, firebase) => [
  `/playlists/${props.auth.uid}/`
])

Sorry. thats what I have, i forgot to update the code, now updated the code. I am connecting with ${props.auth.uid} and still getting null.

Ok. Found Out what the issue was. I feel so stupid :/

For test purpose I manually created the document in Firebase by copying the userID from Firestore, where the profiles are stored. But somehow I copied 2Fjze7uyWtpAXmqy0085G6h8In8vI2 instead of jze7uyWtpAXmqy0085G6h8In8vI2. HA FAIL!

Firebase Datastructure

So, Now that I changed the document id with the actual userID everything is working as usual. Thanks for your attention.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

NirLud picture NirLud  路  3Comments

wildfrontend picture wildfrontend  路  3Comments

benoj picture benoj  路  4Comments

darrenfurr picture darrenfurr  路  5Comments

katha247 picture katha247  路  4Comments