3

I am new to React Native. In my app, the same function to check if user exists in the database is used in 3 different files.

checkUserExists = (userId) => {
        var that = this;
        database.ref('users').child(userId).once('value').then(function(result){
        const exists = (result.val() !== null);

        if (exists) {
            var data = result.val();
            that.setState({
                username: data.username,
                name: data.name
            })
        }
    })
}

To reduce amount of code, I want to put this function into a component so that it can be reused. So I created a new file with this function:

export function checkUserExists (userId) {
    database.ref('users').child(userId).once('value').then(function(result){
        const exists = (result.val() !== null);

        if (exists) {
            var data = result.val();
            setState({
                username: data.username,
                name: data.name
            })
        }
    })
}

But this is met with [Unhandled promise rejection: ReferenceError: Can't find variable: setState]

What is the proper way to handle this? Do I have to use Redux? I can't find a clear answer on SO so far.

Update: I also tried

export default class Authentication extends Component {
    checkUserExists = (userId) => {
        database.ref('users').child(userId).once('value').then(function(result){
            const exists = (result.val() !== null);

            if (exists) {
                var data = result.val();
                setState({
                    username: data.username,
                    name: data.name
                })
            }
        })
    }
}

and I tried to call it like

Authentication.checkUserExists(user.id);

but gets: TypeError: _authentication.default.checkUserExists is not a function

JAM
  • 740
  • 3
  • 15
  • 33

2 Answers2

1

Two things you can do to make it work

  1. Change the .then function to an arrow function or bind it
  2. Call the checkUserExists function where you execute it with the class context
 export function checkUserExists (userId) {
        database.ref('users').child(userId).once('value').then((result) => {
            const exists = (result.val() !== null);

            if (exists) {
                var data = result.val();
                this.setState({
                    username: data.username,
                    name: data.name
                })
            }
        })
    }

Now where you wanna use checkUserExists, you would use it with .call like

checkUserExists.call(this, userId);

One downside of this solution though is that it might be difficult to debug your app as the state is being set from code that is outside of the component

To overcome the downside you can define your method like

export async function checkUserExists (userId) {
      try {
        const result = await database.ref('users').child(userId).once('value');
        const exists = (result.val() !== null);
        if (exists) {
            return data;
        }
        return false;
      } catch(e) {
         console.log(e);

      }
  }

and in the component use it like

someFunc = async () => {
   const data = await checkUserExists(userId);
   if (data) {
        this.setState({
            username: data.username,
            name: data.name
        })
   }
}
Shubham Khatri
  • 270,417
  • 55
  • 406
  • 400
  • Is the first approach the only way for me to do setState in the component itself? The second approach passes data back to the caller. I am not super familiar with `function.call()` format – JAM Feb 24 '19 at 03:57
  • @JacobMarsellos, yes the first way is the only way to do setState outside of the component. You can read about [`.call here`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call) The other method that might come handy for you are .apply and .bind You can check the difference between them [here](https://stackoverflow.com/questions/15455009/javascript-call-apply-vs-bind) – Shubham Khatri Feb 24 '19 at 06:35
  • As you mentioned, I'd like to avoid potentially difficult debugging. So the 2nd solution seems better. But, the 2nd solution would still force repeated code in different files because the callback will always be the same right? – JAM Feb 24 '19 at 17:11
  • Indeed the second solution should be the way to go but it still has a slight redundancy – Shubham Khatri Feb 24 '19 at 17:13
0

You need to call this.setState from within a component. You can't just call if from anywhere.

Your helper function is not bound to your class. Notice in the original var that = this is set up so that that.setState will work from within the then block. It's likely that checkUserExists is bound using https://github.com/tc39/proposal-class-fields in your original example. Now you've got a regular (not bound to the class) function and it has no access to this nor any of its methods, including setState.

To be honest though, this is an XY problem, imho. If you want to extract a helper function, you should have it return the data you need, not attempt to pass around references to the class whose state you want to update. You should instead just return the data to the component --and you have to do this because your database call is async anyway-- and use that data within the component to call its own setState method.

https://reactjs.org/docs/react-component.html#setstate

jmargolisvt
  • 5,722
  • 4
  • 29
  • 46
  • Updated my answer. Everything in the other guy's post is right too and addresses the same issue regarding binding. – jmargolisvt Feb 24 '19 at 04:29