bartonhammond / snowflake

:snowflake: A React-Native Android iOS Starter App/ BoilerPlate / Example with Redux, RN Router, & Jest with the Snowflake Hapi Server running locally or on RedHat OpenShift for the backend, or a Parse Server running locally or remotely on Heroku
http://bartonhammond.github.io/snowflake/snowflake.js.html
MIT License
4.59k stars 613 forks source link

Parse GET from Classes by ID Not Working inside Actions #168

Closed iSamuelBarney closed 7 years ago

iSamuelBarney commented 7 years ago

YellowBox.js:69 Possible Unhandled Promise Rejection (id: 0): Cannot read property 'getScrollableNode' of undefined TypeError: Cannot read property 'getScrollableNode' of undefined at AnimatedComponent._detachNativeEvents (http://localhost:8081/index.ios.bundle?platform=ios&dev=true&minify=false&hot=true:54506:24) at AnimatedComponent.componentWillUnmount (http://localhost:8081/index.ios.bundle?platform=ios&dev=true&minify=false&hot=true:54467:6) at http://localhost:8081/index.ios.bundle?platform=ios&dev=true&minify=false&hot=true:23355:13 at measureLifeCyclePerf (http://localhost:8081/index.ios.bundle?platform=ios&dev=true&minify=false&hot=true:23021:8) at ReactCompositeComponentWrapper.unmountComponent (http://localhost:8081/index.ios.bundle?platform=ios&dev=true&minify=false&hot=true:23354:1) at Object.unmountComponent (http://localhost:8081/index.ios.bundle?platform=ios&dev=true&minify=false&hot=true:21620:18) at ReactCompositeComponentWrapper.unmountComponent (http://localhost:8081/index.ios.bundle?platform=ios&dev=true&minify=false&hot=true:23364:17) at Object.unmountComponent (http://localhost:8081/index.ios.bundle?platform=ios&dev=true&minify=false&hot=true:21620:18) at ReactCompositeComponentWrapper.unmountComponent (http://localhost:8081/index.ios.bundle?platform=ios&dev=true&minify=false&hot=true:23364:17) at Object.unmountComponent (http://localhost:8081/index.ios.bundle?platform=ios&dev=true&minify=false&hot=true:21620:18)console.warn @ YellowBox.js:69onUnhandled @ Promise.js:25onUnhandled @ rejection-tracking.js:71(anonymous function) @ JSTimers.js:78callTimer @ JSTimersExecution.js:99callTimers @ JSTimersExecution.js:140__callFunction @ MessageQueue.js:234(anonymous function) @ MessageQueue.js:105guard @ MessageQueue.js:45callFunctionReturnFlushedQueue @ MessageQueue.js:104onmessage @ debuggerWorker.js:44

What am I doing wrong? I'm trying to use GET from /classes//

Where should I be making these calls or how as no examples are included anywhere so I'm just spinning my wheels keep getting this error.

iSamuelBarney commented 7 years ago

Inside Parse.js this is what my code looks like

async getHost (hostId) { return await this._fetch({ method: 'GET', url: '/classes/hosts/' + hostId }) .then((response) => { return response.json().then(function (res) { if ((response.status === 200 || response.status === 201)) { return res } else { throw (res) } }) }) .catch((error) => { throw (error) }) }

bartonhammond commented 7 years ago

From here: http://parseplatform.github.io/docs/rest/guide/#objects it looks like the URL should be /1/classes/hosts/hostId but you have /classes/hosts/hostId, ie, maybe you need the prefix /1/?

iSamuelBarney commented 7 years ago

well the endpoint is actually... '/classes/Hosts/'+hostId (correct & tested)

I'm just trying to get data other than the user profile and having a hard time as I'm coming from using sub/unsub/etc so this structure makes no sense to me since my GET call doesn't seem to work either. Is there no guide or example of using Parse in snowflake as far as data?

bartonhammond commented 7 years ago

All of Parse calls are in that Parse.js but there should be examples in Github just need to search, i guess.

On Thu, Nov 17, 2016 at 3:42 PM, iSamuelBarney notifications@github.com wrote:

well the endpoint is actually... '/classes/Hosts/'+hostId (correct & tested)

I'm just trying to get data other than the user profile and having a hard time as I'm coming from using sub/unsub/etc so this structure makes no sense to me since my GET call doesn't seem to work either. Is there no guide or example of using Parse in snowflake as far as data?

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/bartonhammond/snowflake/issues/168#issuecomment-261378760, or mute the thread https://github.com/notifications/unsubscribe-auth/ABORPARJBIlATvJ7z5uiWtZ2-7yuhQooks5q_MpMgaJpZM4K1t8B .

iSamuelBarney commented 7 years ago

I've modified everything a bit with but now I only get a promise back not the data... hmm

iSamuelBarney commented 7 years ago

But no more errors...

iSamuelBarney commented 7 years ago

looking @ reducers maybe thats what I'm needing...

bartonhammond commented 7 years ago

Maybe @wookiem can help, I really haven't done any development w/ the Parse open source server but he has, I believe.

On Thu, Nov 17, 2016 at 3:53 PM, iSamuelBarney notifications@github.com wrote:

I've modified everything a bit with but now I only get a promise back not the data... hmm

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/bartonhammond/snowflake/issues/168#issuecomment-261381574, or mute the thread https://github.com/notifications/unsubscribe-auth/ABORPMXQjWFiLC_0as59Elg-g0hx_WQ1ks5q_MzXgaJpZM4K1t8B .

iSamuelBarney commented 7 years ago

Well I'll keep tinkering & hope @wookiem has time to help. Should figure this out hopefully soon lol

wookiem commented 7 years ago

I use the JavaScript API for the main part of my application and it's very straightforward.

iSamuelBarney commented 7 years ago

could you expand on that a bit?

iSamuelBarney commented 7 years ago

do you just use the session token to call from inside the component that renders then & import parse/react-native?

iSamuelBarney commented 7 years ago

Do you have any example code you can share?

iSamuelBarney commented 7 years ago

@wookiem I get a promise object back with no data when I use the actions/reducers which might be done wrong and probably is. There has to be a simple way to just grab the data once logged in as that's working fine, just can't get anything else.

iSamuelBarney commented 7 years ago

nvm @wookiem & @bartonhammond so sorry the issue was with setting the REST key & not a JS key...lol lesson learned

bartonhammond commented 7 years ago

Good for you.

It happens to all of us, i was looking for my glasses everywhere this morning before I realized I was wearing them...

On Nov 17, 2016 5:43 PM, "iSamuelBarney" notifications@github.com wrote:

nvm @wookiem https://github.com/wookiem & @bartonhammond https://github.com/bartonhammond so sorry the issue was with setting the REST key & not a JS key...lol lesson learned

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/bartonhammond/snowflake/issues/168#issuecomment-261405763, or mute the thread https://github.com/notifications/unsubscribe-auth/ABORPCD8t5xmg1Yv6AXD4ZPGTDJI6yb0ks5q_OaXgaJpZM4K1t8B .

wookiem commented 7 years ago

I am using a more recent version of Parse in package.json:

"dependencies": {
     "parse": "1.9.2",

I initialize Parse within src/snowflake.js:

import Parse from 'parse/react-native';
import CONFIG from '../lib/config';

Parse.initialize( CONFIG.PARSE.appId);
Parse.serverURL = (CONFIG.backend.parseLocal)
? CONFIG.PARSE.local.url
: CONFIG.PARSE.remote.url

I keep track of the currently logged in user and the sessionToken in a separate Redux state called "currentUser". Here's its reducer (note how it ties in with existing snowflake redux states). [Edited based on Barton's comment below]

const {
  GET_PROFILE_SUCCESS,
  SIGNUP_SUCCESS,
  LOGIN_SUCCESS,
  SESSION_TOKEN_SUCCESS,

  LOGOUT_SUCCESS,  
} = require('../../lib/constants').default;

export default function currentUser ( state = {}, action ) {
    let currentUser = null;

  switch (action.type) {
        case SIGNUP_SUCCESS:
        case LOGIN_SUCCESS:
        case GET_PROFILE_SUCCESS:
            currentUser = action.payload.sessionToken;
            return Object.assign({}, {
                    objectId: currentUser.objectId,
                    sessionToken: currentUser.sessionToken,
                    username: currentUser.firstName,
                });     
        case SESSION_TOKEN_SUCCESS:
            currentUser = action.payload.sessionToken;
            return Object.assign({}, {
                    objectId: currentUser.objectId,
                    sessionToken: currentUser.sessionToken,
                    username: currentUser.firstName,
                }); 

        case LOGOUT_SUCCESS:
            return {};

        default: 
            return state;
  }
}

Then, here's sample code for saving data to Parse. A query example, follows the same pattern. Note, "store" refers to the redux store.

import Parse from 'parse/react-native';

export function parseSaveProject( store, dispatch, projectId ) {
    var CurrentProject = Parse.Object.extend("CurrentProject");
    var currentProject = new CurrentProject();

    currentProject.set("projectId", project);

    let sessionToken = store.getState().currentUser.sessionToken;

    currentProject.save(null, {
        sessionToken: sessionToken,
        success: function(currentProject) {
            reduxUpdateParseProjectId(dispatch, currentProject.id);
        },
        error: function(currentProject, error) {
            console.log("Error: Parse did not update current project: ", project.id);           
        }
    });
}

And here's a sample react-native component that would make the call to parseSaveCurrentProject:

class Projects extends Component {

    clickHandler(projectId) {
        var store = this.context.store;
        var dispatch = this.props.dispatch;

        parseSaveProject(store, dispatch, projectId);
    }

  render() {
     return (
      <View onClick={this.clickHandler.bind(this, projectId)}>
        <Text>Click on current Project</Text>
            </View>
        );  
  }
}

Projects.contextTypes = {
    store: React.PropTypes.object
}

const mapStateToProps = (state) => {
    return {
        currentUser: state.currentUser,
    };
};

const mapDispatchToProps = (dispatch) => {
    return { dispatch };
};

export default connect(
        mapStateToProps,
        mapDispatchToProps
)(Projects);
bartonhammond commented 7 years ago

Looks to me that some of those case blocks could be merged as they are identical?

On Nov 17, 2016 7:07 PM, "wookiem" notifications@github.com wrote:

I am using a more recent version of Parse in package.json:

"dependencies": { "parse": "1.9.2",

I initialize Parse within src/snowflake.js:

import Parse from 'parse/react-native'; import CONFIG from '../lib/config';

Parse.initialize( CONFIG.PARSE.appId); Parse.serverURL = (CONFIG.backend.parseLocal) ? CONFIG.PARSE.local.url : CONFIG.PARSE.remote.url

I keep track of the currently logged in user and the sessionToken in a separate Redux state called "currentUser". Here's its reducer (note how it ties in with existing snowflake redux states).

const { GET_PROFILE_SUCCESS, SIGNUP_SUCCESS, LOGIN_SUCCESS, SESSION_TOKEN_SUCCESS,

LOGOUT_SUCCESS, } = require('../../lib/constants').default;

export default function currentUser ( state = {}, action ) { let currentUser = null;

switch (action.type) { case SIGNUP_SUCCESS: currentUser = action.payload; return Object.assign({}, { objectId: currentUser.objectId, sessionToken: currentUser.sessionToken, username: currentUser.firstName, }); case LOGIN_SUCCESS: currentUser = action.payload; return Object.assign({}, { objectId: currentUser.objectId, sessionToken: currentUser.sessionToken, username: currentUser.firstName, }); case SESSION_TOKEN_SUCCESS: currentUser = action.payload.sessionToken; return Object.assign({}, { objectId: currentUser.objectId, sessionToken: currentUser.sessionToken, username: currentUser.firstName, }); case GET_PROFILE_SUCCESS: currentUser = action.payload; return Object.assign({}, { objectId: currentUser.objectId, sessionToken: currentUser.sessionToken, username: currentUser.firstName, });

    case LOGOUT_SUCCESS:
        return {};

    default:
        return state;

} }

Then, here's sample code for saving data to Parse. A query example, follows the same pattern. Note, "store" refers to the redux store.

import Parse from 'parse/react-native';

export function parseSaveProject( store, dispatch, projectId ) { var CurrentProject = Parse.Object.extend("CurrentProject"); var currentProject = new CurrentProject();

currentProject.set("projectId", project);

let sessionToken = store.getState().currentUser.sessionToken;

currentProject.save(null, {
    sessionToken: sessionToken,
    success: function(currentProject) {
        reduxUpdateParseProjectId(dispatch, currentProject.id);
    },
    error: function(currentProject, error) {
        console.log("Error: Parse did not update current project: ", project.id);
    }
});

}

And here's a sample react-native component that would make the call to parseSaveCurrentProject:

class Projects extends Component {

clickHandler(projectId) {
    var store = this.context.store;
    var dispatch = this.props.dispatch;

    parseSaveProject(store, dispatch, projectId);
}

render() { return ( <View onClick={this.clickHandler.bind(this, projectId)}>

Click on current Project
        </View>
    );

} }

Projects.contextTypes = { store: React.PropTypes.object }

const mapStateToProps = (state) => { return { currentUser: state.currentUser, }; };

const mapDispatchToProps = (dispatch) => { return { dispatch }; };

export default connect( mapStateToProps, mapDispatchToProps )(Projects);

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/bartonhammond/snowflake/issues/168#issuecomment-261419807, or mute the thread https://github.com/notifications/unsubscribe-auth/ABORPOFmnPwbxY1AulzgebxvRh6MYpC3ks5q_PpFgaJpZM4K1t8B .

wookiem commented 7 years ago

Yeah, that's a good point!

iSamuelBarney commented 7 years ago

@wookiem do you have an example of a query? I'm just getting some issues when trying to use the Parse.js file here is my code:

async getHost (hostId) { return await this._fetch({ method: 'GET', url: '/classes/Hosts/' + hostId, }) .then((response) => { return response.json().then(function (res) { if ((response.status === 200 || response.status === 201)) { return res } else { throw (res) } }) }) .catch((error) => { throw (error) }) }

Not sure what I'm doing wrong here

iSamuelBarney commented 7 years ago

@wookiem getting Error: Permission denied for action get on class Hosts.

wookiem commented 7 years ago

The error suggests that Parse class instance hasn't been initialized properly. When you put a breakpoint on the return await this._fetch({ line, do you see that this._sessionToken, this._applicationId and this._ApiBaseUrl are properly set?

If not, then you'll need to add getHost as another method using the pattern established in src/lib/Backend.js and src/lib/BackendFactory.js.

Then you should be able to initiate the promise chain with the following:

BackendFactory().getHost(hostId)
.then((res) {
    // process res
})
.catch((error) {
    // process error
})
iSamuelBarney commented 7 years ago

Got it working!!!! Thanks all I had to do was add the global.currentUser with the token as an argument just like you suggested. Thanks!!

iSamuelBarney commented 7 years ago

Thanks for all the help @wookiem one more quick question lol. How would I add relation query constraints like:

where={"phostId":{"__type":"Pointer","className":"Hosts","objectId":"<ID>"}}

it shows in curl curl -X GET ... -G \ --data-urlencode "where={\"phostId\":{\"__type\":\"Pointer\",\"className\":\"Hosts\",\"objectId\":\"\"}}" \ .../classes/Residents

How do i add --data-urlencode into my request?

async getResidents (hostId) { // where={"phostId":{"__type":"Pointer","className":"Hosts","objectId":"<ID>"}} return await this._fetch({ method: 'GET', url: '/classes/Residents/' + hostId, }) .then((response) => { return response.json().then(function (res) { if ((response.status === 200 || response.status === 201)) { return res } else { throw (res) } }) }) .catch((error) => { throw (error) }) }

wookiem commented 7 years ago

I'm not sure. After logging in, I switch gears and use the Parse Javascript API for all of my Parse database interactions (i.e. I don't try to use the REST API). I show how to make this switch in my sample code from a couple weeks ago (see above). I find it a lot more intuitive than using the REST API...

iSamuelBarney commented 7 years ago

Yea seems like I should do the same then.

iSamuelBarney commented 7 years ago

I got it working!!

FYI if you wanted to see how below.

import qs from 'querystring' async getResidents (hostId) { let query = qs.stringify({ where: {"phostId":{"__type":"Pointer","className":"Hosts","objectId":"${hostId}"}} }) return await this._fetch({ method: 'GET', url: '/classes/Residents?' + query, }) .then((response) => { return response.json().then(function (res) { if ((response.status === 200 || response.status === 201)) { return res } else { throw (res) } }) }) .catch((error) => { throw (error) }) }

bartonhammond commented 7 years ago

Good job @iSamuelBarney