1

I'm new to React-Native and I was confused on how to have a variable that can be accessed by all the functions within a file without being in a class.

My issues is that I assigned storage in taskFour() and I want that value to be returned in runDemo() but for some reason when I console.log(storage) in runDemo() it returns undefined!

I have defined a helper file with a bunch of functions that call upon each other.

Helper.js

import React from 'react';
import SQLite from 'react-native-sqlite-storage';

let db;
let storage;

function runDemo() {
  loadAndQueryDB();
 //Suppose to return value assigned in queryPeopleSuccess but console logs 'undefined'
  console.log(storage);
  return storage;
}

//    Sends an update saying that Database was successfully opened
function openCB() {
  console.log("Success Opening DB");
}

//      Sends an update with error message and returns FALSE
function errorCB(err) {
  console.log("SQL Error: ", err);
  return false;
}

/** 2. Called when runDemo is called **/
/*   assigns variable 'db' to opened Database */
/*   Calls queryPeople(db) */
function loadAndQueryDB() {
  console.log("Opening Database...: ");
  
  db = SQLite.openDatabase({ name: "users.db", createFromLocation: 1}, openCB, errorCB);
  queryPeople(db);
}

/** 3. Called when loadAndQueryDB is called **/
/*    Get the DB and applies a SQL call that if successful will call queryPeopleSuccess*/
function queryPeople(db) {
  console.log("Executing employee query...");
  //Execute a database transaction.
  db.transaction((tx) => {
    tx.executeSql('SELECT * FROM users', [], queryPeopleSuccess, errorCB);
  });

}

function queryPeopleSuccess(tx, results) {
  var len = results.rows.length;
  let localArray = [];
  //Go through each item in dataset
  for (let i = 0; i < len; i++) {
    let row = results.rows.item(i);
    localArray.push(row);
  }

  storage = localArray;
}

export {
  runDemo,
}

I thought by assigning "storage" outside of the functions would make it a variable accessible to all the functions in this file without making it a global variable. In addition, one restriction I have is that I can't return storage from queryPeopleSuccess all the way back to runDemo due to functions within these functions that are not suppose to have a return value!

Could someone please point out how I can have a variable within a file that does not need to be in a class that can be accessed and edited by the functions within that file?

EDITED: Edited for clarity and typos. Turns out the issue with my code is due to async!

Alan Chen
  • 127
  • 2
  • 13
  • Your assumption is correct, what exactly is the problem you are facing? You only described what should happen but not what actually happens instead. – CherryDT Jun 29 '20 at 22:21
  • Apologies! What actually happens is that when I console.log(storage) in the runDemo() function it returns undefined when it is suppose to return the value i assigned it to in taskFour() – Alan Chen Jun 29 '20 at 22:22
  • After fixing the missing parts (`taskThree`, `localArray`) it works for me as expected: https://jsfiddle.net/c59fLujr/ - Please create a [minimal, reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) that demonstrates problem. – CherryDT Jun 29 '20 at 22:25
  • Maybe you have a `let storage`/`var storage` or a function argument `storage` inside your real `taskFour`, so you are assigning to _that_ variable instead of the global one? - Or: Maybe part of your workflow is asynchronous, and you are writing to `storage` after your `runDemo` has long returned... – CherryDT Jun 29 '20 at 22:26
  • Unfortunately, I don't – Alan Chen Jun 29 '20 at 22:27
  • So then please try to remove parts from your code until you arrive at a minimal, reproducible example that works in a JSFiddle like my test above. My test works, so the only way to understand why your code is not working is to see it runnable in JSFiddle in its non-working way and analyze it there. – CherryDT Jun 29 '20 at 22:28
  • My code uses a react-native-sqlite-storage library which I don't think can be reproduced in JSFiddle but I can edit my code above to show more – Alan Chen Jun 29 '20 at 22:32
  • You could mock it, the idea is to create a minimal example, how this library works internally is probably irrelevant. And if by minimizing your example your bug goes away, then you found the bug! But your mention of that library and that it's apparently an important part of this task flow sounds as if there _is_ asynchronicity involved. If you have code like `function taskOne () { tx.executeSql('SELECT 1', [], function (tx, results) { taskTwo(results) }) }` then this is the problem: `taskOne` returns before `taskTwo` is even called. – CherryDT Jun 29 '20 at 22:34
  • I'm not trying to mock anything I just simply thought that my example was already at the minimum without complicating things with the library. I've never dealt with asynchronicity and I really am confused on how to proceed – Alan Chen Jun 29 '20 at 22:37
  • Besides using the wrong variable name (`database`?), the code is asynchronous (that’s why you have to pass a callback). Synchronously returning the result of an async function is not possible. Read up on promises and `async/await` – Felix Kling Jun 29 '20 at 22:41
  • My bad. I changed some variables names around and forgot to change that one to storage. However I don't think that is the issue... But thanks I'll read up on Promises and async/await in the meantime – Alan Chen Jun 29 '20 at 22:44

3 Answers3

1

taskFour is never called ? You are calling taskThree from taskTwo.

1

Your issue is related to async in Javascript.

Your current program ( script ) is executed from top to bottom with sync mechanism, and you did not assign any initial value to your variable storage. So the function runDemo with return the default value undefined.

To solve your problem you need to convert your runDemo to async Function.

let storage;

async function runDemo() {
    await taskOne();
    //Suppose to return value assigned in taskFour but console logs 'undefined'
    console.log(storage);
    return storage;
}


function taskOne() {
    taskTwo();
}

function taskTwo() {
    taskFour();
}

let localArray = 20;

function taskFour() {

    storage = localArray;
    //Here storage is assigned properly and outputs the data I want
    console.log(storage);
}
runDemo()
1

Your problem is that runDemo has long returned before queryPeopleSuccess is even writing to storage. So, you are reading it before it gets written, and since information can't travel back in time, you get undefined. Basically you have this problem.

You could greatly simplify your code by using an asynchronous function instead:

import SQLite from 'react-native-sqlite-storage'

export async function runDemo () {
  console.log('Opening Database...: ')

  try {
    // This one is tricky, because it seems the library synchronously
    // returns the db instance, but before that it calls the success or
    // error callbacks (without passing the instance).
    const db = await new Promise((resolve, reject) => {
      const db = SQLite.openDatabase(
        { name: 'users.db', createFromLocation: 1 },
        // The trick here is that in the success case we wait for openDatabase
        // to return so that db will be assigned and we can resolve the promise
        // with it.
        () => setTimeout(() => resolve(db), 0),
        reject
      )
    })
    console.log('Success Opening DB')

    console.log('Executing employee query...')
    // Execute a database transaction.
    const results = await new Promise((resolve, reject) => {
      db.transaction(tx => {
        tx.executeSql(
          'SELECT * FROM users',
          [],
          (tx, results) => resolve(results),
          reject
        )
      })
    })

    const localArray = []
    // Go through each item in dataset
    for (let i = 0; i < results.rows.length; i++) {
      localArray.push(results.rows.item(i))
    }
    return localArray
  } catch (e) {
    console.log('SQL Error: ', e)
    return false
  }
}

Then you would call runDemo as console.log(await runDemo()) inside another async function, or runDemo().then(results => console.log(results), err => console.error(err)) otherwise.

CherryDT
  • 25,571
  • 5
  • 49
  • 74
  • I see. Thank you so much! I am importing helper.js into my App.js and I was wondering if I could just call it with const data = Helper.runDemo(); or it seems like I need something related to promises or async! – Alan Chen Jun 29 '20 at 23:00
  • Yes you will have the same problem there again - once one thing is async, _everything_ that depends on the result of that one thing (directly or indirectly) has to be become async as well. – CherryDT Jun 29 '20 at 23:03
  • Ahhhh ok. Thank you so much for your help! I'll definitely read up on these – Alan Chen Jun 29 '20 at 23:04