Skip to content
Advertisement

React Native app’s expo-sqlite callback not executed until app refresh

I have a simple app that makes a connection to a sqlite database, reads results from that database, and displays them. Here is the app:

import React, { useEffect, useState } from 'react';
import { View, Text } from 'react-native'

import * as SQLite from "expo-sqlite";
import * as FileSystem from 'expo-file-system';
import { Asset } from 'expo-asset';


async function openDatabase() {
  if (!(await FileSystem.getInfoAsync(FileSystem.documentDirectory + 'SQLite')).exists) {
    await FileSystem.makeDirectoryAsync(FileSystem.documentDirectory + 'SQLite');
  }
  await FileSystem.downloadAsync(
    Asset.fromModule(require('./assets/banana-dict.db')).uri,
    FileSystem.documentDirectory + 'SQLite/banana-dict.db'
  );
  return SQLite.openDatabase('banana-dict.db');
}


export default function App() {
  const [ dicts, setDicts ] = useState([]);
  const [ db, setDb ] = useState({
    transaction: () => {
      return {
        executeSql: () => {},
      };
  }});

  const updateDicts = (a) => {
    setDicts(
      Object.fromEntries(a.map(({rowid, ...rest}) => [rowid, rest]))
    );
  }

  const fetchDicts = () => {
    console.log('fetch')

    db.transaction((tx) => {
      tx.executeSql(
        `select rowid, * from dicts;`,
        [],
        (_, { rows: { _array } }) => { console.log(_array); updateDicts(_array); }
      );
    });
  }

  useEffect(() => {
    openDatabase().then((value) => {
      setDb(value);
      fetchDicts();
    });
  }, []);

  return (
    <View style={{marginTop: 100}}>
      <Text>Open App.js to start working on your app!</Text>
      <Text>{JSON.stringify(dicts)}</Text>
    </View>
  );
}

banana-dict.db is a preexisting database I’m using for this app. I’ve found that if I start the app and open it on my phone, the dicts state (ie, results of a query on the db) do not appear. However, if I press r in the console for a refresh, the results appear immediately. Anyone know why this is?

Advertisement

Answer

When the app starts, the initial db value is set. The first effect batches setDb and fetchDicts, so db.transaction has not been updated with the value of the setDb call. Because Expo has stateful refresh, the db is no longer set as the initial value but has the result of setDb, so fetchDicts works, and the results appear.

One way to fix this would be to move fetchDicts into its own effect, and call it when db changes.

useEffect(() => {
  openDatabase().then((value) => {
    setDb(value);
  });
}, []);

useEffect(() => {
  fetchDicts(); // will do nothing until db connection is open
}, [db]);
Advertisement