10

Description: I'm making a react native app, where I have a list of github users that I'm following and I want to implement the functionality to unfollow and refresh the list.

I made two asynchronous helpers to interact with the github API, one to unfollow the user(via PUT) and another one to get the list of followings(via GET). I also added a firebase listener in the list of followings component. Each following would navigate me to an individual profile view consisted of a unfollow button. When I click a button, it should unfollow the user, update the list of followings in the component and then navigate back to the list of followings component.

Problem Unfollowing the user is working as expected, but the list of followings view still contains the old list. My code is returning back the old data, even though the github api is returning the new updated data, so I'm suspecting the problem must be the way I'm using async/await.

I even made a refresh button to refresh the list of followings, but the new data only gets returned sometimes, like 1/20 times.

UPDATE: After testing multiple scenarios, I don't think firebase is the problem since fetch is returning the same old data. I think the main problem might lie in the fetch call. I've tested grabbing the data from Postman as well and it grabs the correct data.

It seems like fetch is not working as expected since response.json() contains the old data. I've made a simple jsfiddle here (you need to supply your own access token) that shows get_following -> unfollow -> get_following working successfully, aka the following data are modified. However, in my app the fetch returns the same old following data before the unfollow even though the github website UI shows the change and Postman returns the new modified data. I've also updated the code a little bit.

Code

List of followings

    /**
     * Listens for any changes on the database and updates the
     * dataSource accordingly.
     * @param {Firebase Object} ref
     */
    _listenForData(ref) {
        ref.on('value', (snapshot) => {
            this.setState({
                dataSource: snapshot.val()
            },
            this.setState({
                isLoading: false,
            }));
        });
    }

    componentDidMount() {
        // Sets up the listener for realtime changes.
        GithubApi.get_followings(this.ref)
        .then(_ => this._listenForData(this.ref));
    }

Individual User with unfollow button

async unfollow() {

    try {
        let success = await GithubApi.unfollow_user(this.state.login);
        console.log('unfollowed user');
        console.log(success);
        if (success) {
            // Updates database after unfollowing
            console.log('update followings')
            await GithubApi.get_followings(this.ref);
            return true;
        }
    } catch (error) {
        console.error(error);
        return false;
    }
}

render() {
    const { goBack } = this.props.navigation;
    return (
        <Button
            title='Unfollow'
            onPress={
                () => this.unfollow().then(_ => goBack())
            }
        />
    )
}

Github Api Helpers

const GithubApi = {
    url: 'https://api.github.com/',
    access_token: ...,

    /**
     * An asychronous helper function to grab data from github
     * given an url and add data the firebase.
     *
     * @param {string} url
     * @param {Firebase Object} firebaseRef
     */
    _get_data_from_github_with_firebase: async function(url, firebaseRef) {
        try {
            let response = await fetch(url);
            let responseStatus = await response.status;
            let responseJson = await response.json();
            if (responseStatus === 200) {
                firebaseRef.set(responseJson,
                (error) => {
                    if (error) {
                        console.log(error);
                        return false;
                    } else {
                        return true;
                    }
                });
            }
            return false;
        } catch (error) {
            return false;
        }
    },

     /**
     * Gets the user's following data and adds it to the database.
     * @param {Firebase Object} firebaseRef
     */
    get_followings: async function(firebaseRef) {
        return await this._get_data_from_github_with_firebase(
            this.url + 'user/following?' + this.access_token,
            firebaseRef
        );
    },

    unfollow_user: async function(username) {
        try {
            let url = this.url + 'user/following/' + username + '?' + this.access_token;
            let response = await fetch(url, { method: 'DELETE'});
            let responseStatus = await response.status;
            if (responseStatus === 204) {
                return true;
            }
            return false;
        } catch (error) {
            return false;
        }
    },
ygongdev
  • 264
  • 3
  • 19
  • What does `GithubApi.unfollow_user()` look like? / What is `this.state.login`? – Arman Charan Oct 29 '17 at 02:42
  • @ArmanCharan, sorry I just updated the the code snippet to include `unfollower_user`. `this.state.login` is just the user's github login username. – ygongdev Oct 29 '17 at 02:47
  • All good bro. Did you mean to define your `GithubApi` object as a [Class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes)? I've never really seen anyone call `this` on an object like that before. – Arman Charan Oct 29 '17 at 02:48
  • You might also like to try [template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) for constructing your HTTP requests. – Arman Charan Oct 29 '17 at 02:49
  • Yeah, I'm definitely going to refactor later. I'm just trying to solve this problem atm. – ygongdev Oct 29 '17 at 02:52
  • You appear to be using `await` correctly. Assuming the `GithubApi` requests are definitely working, the problem is likely Firebase or React related. I'd be trying things such as removing the `async` prefix from your `componentDidMount()` function and maybe nesting the queued functions within it into their own `async` function. Messing directly with the definitions of lifecycle functions has caused me a bit of obscure grief in the past. – Arman Charan Oct 29 '17 at 03:25
  • by the way, there's no need to await here `let responseStatus = await response.status;` - that's not a problem, it's just bad code ... the code inside `if (responseStatus === 200) {` looks wrong .. why are you bothering to return true/false inside `firebaseRef.set(responseJson, (error)` ... a) if that's asynchronous, then returning a value is redundant, and b) if it isn't, it's still redundant unless the .set callback expects a true or false return value, I guess – Jaromanda X Oct 29 '17 at 03:30
  • @ArmanCharan, that sounds reasonable, I'll try playing around with that. – ygongdev Oct 29 '17 at 03:35
  • @JaromandaX, you're right about `reponseStatus`, for some reason I thought it was a promise. I added the callback to `firebaseRef.set(..)` b/c I I was trying to make sure that the `set` operation finished before moving on. I guess I didn't need it in this case. I needed the `get_data...` function to return a boolean to render the different UIs. – ygongdev Oct 29 '17 at 03:40
  • If firebase.set returns a promise try returning that – Jaromanda X Oct 29 '17 at 03:48

3 Answers3

8

Try this:

let response = await fetch(url, {
  headers: {
    'Cache-Control': 'no-cache'
  }
});
adiga
  • 34,372
  • 9
  • 61
  • 83
Mohammed Nafie
  • 403
  • 1
  • 5
  • 14
3

You are missing await inside _get_data_from_github_with_firebase. Try changing the function to this:

_get_data_from_github_with_firebase: async function(url, firebaseRef) {
        try {
            let response = await fetch(url);
            let responseStatus = await response.status;
            let responseJson = await response.json();
            var result = false
            if (responseStatus === 200) {
                result = await firebaseRef.set(responseJson,
                (error) => {
                    if (error) {
                        console.log(error);
                        return false;
                    } else {
                        return true;
                    }
                });
            }
            return result;
        } catch (error) {
            return false;
        }
    },

Note I highly recommend you use redux-saga for synchronising actions.

Alexander Vitanov
  • 4,074
  • 2
  • 19
  • 22
3

Are you by any chance a former C developer? Or you are used to pass variables by reference?

I think your problem is that inside this function you are trying to return a value by reference:

_get_data_from_github_with_firebase: async function(url, firebaseRef) {
        try {
            let response = await fetch(url);
            let responseStatus = await response.status;
            let responseJson = await response.json();
            var result = false
            if (responseStatus === 200) {
                result = await firebaseRef.set(responseJson,  //You are executing a method by reference
                (error) => {
                    if (error) {
                        console.log(error);
                        return false;
                    } else {
                        return true;
                    }
                });
            }
            return result;
        } catch (error) {
            return false;
        }
    },

Perhaps a more expert JS developer could confirm if this would work but I am not used to do this. I would change the signature and just return the responseJson instead of trying to setting it like this.

Probably responseJson has a value inside this function but is undefined outside.

sebastianf182
  • 9,844
  • 3
  • 34
  • 66