parse-redux
parse-redux copied to clipboard
Accessing User and its attributes
As we have started discussing here on the commit I like to do some follow up discussion.
When I use Parse.User.current() after having logged in a user the object looks like this:
So when I want to use any properties of the user I need to use the getter and map them a new object:
Something like this
getParseUser() {
let user = Parse.User.current()
return {
username: user.getEmail(),
myProp: user.get('myProp')
}
}
But I don't want to maintain the User's Object Attributes on multiple occurrences in my code. So is there a way to access a reference to the User Object that looks a bit more like the server data:
serverData: {
createdAt: Sat Jan 09 2016 14:48:55 GMT+0100 (CET)
sessionToken: "r:CxucYoDheD6bnsc67rvXD2VU5"
test: true
updatedAt: Sat Jan 09 2016 14:48:55 GMT+0100 (CET)
username: "[email protected]"
}
You should be able to get the attributes as you would with an ordinary Parse Object, such as
var user = Parse.User.current()
var test = user.get("test")
Or if you want to get all the attributes at once
var user = Parse.User.current()
var attributes = user.attributes
If that doesn't work, then there is a bug.
Ok. That seems to work.
But how should I now use the attributes in my react components so they re-render when some action manipulates them from somewhere else.
I have a <AuthenticationComponent user={this.getParseUser()} ...
and
getParseUser() {
var user = Parse.User.current()
return (user && user.attributes) || {}
}
But when I log in or log out the user with
handleOnLogOutClick(){
Parse.User.logOut()
}
handleOnAuthenticateClick(username, password) {
Parse.User.logIn(username, password)
}
the AuthenticationComponent does not re-render.
EDIT:
I forgot to mention that I want to be using react-redux in combination with parse-redux.
So worst case: The mutations of parse-redux to the state do not correctly trigger the connect (or whatsoever) of react-redux. That would make me question why.
For that, you should use react-redux.
import { connect } from 'react-redux'
class App extends React.Component {
render() {
var user = Parse.User.current()
var username = user.get('username')
return <div>{username}</div>
}
}
// this will update App whenever the user changes from logged in -> logged out, but
// not when properties on the user change
connect(state => {
var user = state.Parse.User.current
return {user: user}
})(App)
Note that you must return a slice of the actual state as shown here in the connect method, and NOT return Parse.Objects.
For other types of objects, you can return the specific object's slice of state from the state.Parse.Object table, or you can return the object table itself. Returning the table as shown below may degrade performance, depending on your database architecture, etc.
// this will update App whenever any _User is updated.
connect(state => {
var user = state.Parse.Object._User
return {user: user}
})(App)
The above method is good for any kind of Parse.Object table. If you start experiencing performance issues, you may want to start returning the specific object instances (see below), but I would not be concerned with this until performance becomes an issue (or if you can foresee that that slice of the state will be updated very frequently).
// this will update App whenever the specific user is updated.
connect(state => {
var user = state.Parse.Object._User[USER_ID]
return {user: user}
})(App)
Also note that this is an incomplete example and must use the react-redux Provider as shown in the react-redux documentation.
Oh, and with the Provider component, you can get the parse-redux state as shown in the documentation.
Ok. The above are all kind of things I already tried, but this makes me more confident to read it from you. Nevertheless I think there could be issues with this solution. I have been testing it on a train with a tethered internet connection and I fear there are timing issues:
How can I reflect the pending state? OR Isn't the set User Action supposed to be dispatched after having received payload from the server?
Parse/User/SET is called on a number of different occasions, from hooks within the original Parse SDK. In that case above, the payload is null, meaning that the user is logged out. In this case, the user is set to null just after the login command is run and before the payload has been received from the server. The action will be run again after the round trip to the server has completed.
The POST issues you're seeing there are likely issues with your connection.
Unfortunately at this time there isn't library support for pending state on objects (including login, etc). There is only support for pending state on Parse.Query and Parse.Cloud functions.
What parse-redux can tell you is whether there is a user logged in or not - if state.Parse.User.current is not null, there is a logged in user. Otherwise, there is no logged in user.
Ok. Yeah, I was aware that my internet connection was down at that moment. I will need to test some things. I definitely have some kind of state issues so that my components do not get the slices of the user properties passed down and thus do not re-render correctly.
Yeah, it can be tricky. The one thing to watch out for is that because you can get the state from outside of connect() with the react-redux helper methods, you have to be quite judicious about making sure that you're still connecting to all the parts of the state that you're using.
One thing I've found useful in testing is to force-rerender the components (or even the full component tree) when there's a possibility I'm not subscribing to all the right parts of the state. If the components update properly, I know that all my code is correct except for the connection to the Redux state.
OK, good. Then I will go on testing. And according to your comments I see this is not an issue of parse-redux. I close this issue.
FYI: I am trying to come up with some kind of seed for React, Redux and Parse.
Okay, just let me know if you think you've found any kind of bug!
I'm not sure what you mean when you say 'seed' but if you mean that you're making a boilerplate, I've got one going here. I made it mostly as an experiment/place to start projects, so I don't mean for it to be any kind of authoritative resource, but it may still be useful to look at. There's just a small bit of markdown files spread across the repo as reference.
And it's probably also worth mentioning that my boilerplate doesn't have any kind of database setup right now.
It's me again. I re-tested some things. It would be nice if you explained me what I am doing wrong. I was debugging this bit:
function mapStateToProps(state) {
/* Populated by react-webpack-redux:reducer */
var stateUser = state.Parse.User.current;
var parseUser = Parse.User.current();
const props = {
user: (stateUser !== null && parseUser !== null)?parseUser.attributes:stateUser //unimportant comment };
return props;
}
further down in the file the return value is used to do the connect part
export default connect(mapStateToProps, mapDispatchToProps)(App);
While debugging I realized that stateUser and parseUser are not set at the same time after the last action when logging in:
The console shows the action:
I really want to use the parseUser so that I can use the attributes.
Yeah, that's not something that's entirely clean. Parse.User.current() and store.Parse.User.current are not exactly in sync. I think it may have something to do with the way Parse handles the user cache from disk, but I'm not entirely sure.
I have tried a couple patterns that work, but haven't really settled on a 'best practice' way of handling it.
With Parse.User.current()
function mapStateToProps(state) {
var user = Parse.User.current()
var userid = user && user.id
return {userid}
}
With state.Parse.User.current
function mapStateToProps(state) {
var user = state.Parse.User.current
var userid = user && user.id
return {userid}
}
Then you could follow up with this to get the user in either case
class App extends React.Component {
render() {
var { userid } = this.props
if (!userid)
return <LogInComponent />
var User = Parse.Object.extend('_User')
var user = Object.assign(new User(), {id: userid})
return <Dashboard user={user} />
}
}
The advantage of using the first method with Parse.User.current() is that if you're using user state to determine which child components get rendered, you know whether Parse.User.current() will return a user or null in the child components. That is, there's no need to pass the user down through props or anything, since Parse.User.current() can be called anywhere within the child routes.
A bit of warning though - Parse.User.current() !== Parse.User.current(), so you can't return it directly in the mapStateToProps function.
There may be a better way of doing it than what I've presented, or something that should be implemented in the library that would make this easier. Let me know if you figure something out that works well for you!
I'm now wondering if having state.Parse.User.current available is not a good design choice because it adds confusion when used alongside Parse.User.current().
I was digging into this a bit more yesterday - I believe the reason for the inconsistency is the fact that when the current user is set, the Redux state immediately updates, alongside all the connected components. But the Parse.User.current won't have updated until after that cycle has updated.
The other complication is that Parse.User.current() is what initially lets Parse know if there is a user cached on disk. Calling it will start loading the cache from the disk if it exists, which subsequently will update the Redux state. (conversely, if you never call Parse.User.current(), I think Parse wouldn't know there's a user cache)
I haven't extensively looked into it, but this seems like a plausible explanation.
I tested your suggestions from the 3 comments above. And it is really like that. Parse.User.current() does return different responses without firing an action in between. So the change there is not noticeable by any redux structures. And yes, the react components are not bound to the change there. So does it mean that this is an issue of this SDK or is Parse SDK just incompatible to some Redux paradigm? It seems to expose the same semantical state at 2 different points in the state tree with different values. Maybe I mixing things also? Because I am trying to get Data from outside the state tree with Parse.User.current().
I don't think that the SDK is thoroughly incompatible with Redux - it's more that the initial implementation of Parse.User.current() is a bit off.
Ultimately, I'd like this to work smoother, but for now we'll just have to live with some hack workarounds. On my site right now, I ended up resorting to manually refreshing the state tree after user login/signup. It works okay, but it's a bit of a hack, haha.
I'm reopening this issue to investigate when I can.