Search code examples
react-nativewatermelondb

TypeError: Cannot read property 'collections' of undefined WatermelonDB React Native


I am developing a React Native aplication using WatermelonDB as my database framework. I have followed Watermelons docs making models, shema, database and helpers; used withObserables to show data but it keeps throwing error in the title. I still can't find any solution to my problem and am here to ask fellow devs for help. This is my first time using WatermelonDB.

log of an error on android emulator shows that error is at HomeScreen.js

App.tsx

import * as React from 'react';
import MainContainer from './navigation/MainContainer';
import database, {initializeDatabase} from './model/database_users';

function App() {
  React.useEffect(() => {
    // Initialize the database when the component mounts
    initializeDatabase()
      .then(() => {
        console.log('Database initialization complete');
        // Continue with the rest of your application logic
      })
      .catch(error => {
        console.error('Error initializing database:', error);
      });
  }, []);
  return <MainContainer />;
}

export default App;

seedDatabase.js

import {User} from './model/model'; // Import your WatermelonDB model

async function seedDatabase() {
  // Check if there are any users in the database
  const existingUsersCount = await User.query().fetchCount();

  // If the database is empty, insert the initial users
  if (existingUsersCount === 0) {
    try {
      await User.withDatabase(async database => {
        // Insert each user into the database
        for (const userData of initialUsers) {
          await database.action(async () => {
            await User.create(user => {
              user.name = userData.name;
              user.email = userData.email;
              user.password = userData.password;
            });
          });
        }
      });
      console.log('Seed data inserted successfully');
    } catch (error) {
      console.error('Error inserting seed data:', error);
    }
  } else {
    console.log('Database already seeded');
  }
}

export default seedDatabase;

./model/model.js

import {Model} from '@nozbe/watermelondb';
import {field, text} from '@nozbe/watermelondb/decorators';

class User extends Model {
  static table = 'users';
  static associations = {
    sites: {type: 'has_many', foreignKey: 'author'},
  };
  @text('name') name;
  @text('email') email;
  @text('password') password;
}

class Site extends Model {
  static table = 'sites';
  static associations = {
    user: {type: 'belongs_to', key: 'author'},
  };
  @text('site_id') site_id;
  @text('txt') txt;
  @text('image') image;
  @relation('user', 'author') author;
}

.model/shema.js

import {appSchema, tableSchema} from '@nozbe/watermelondb';

const shema = appSchema({
  version: 4,
  tables: [
    tableSchema({
      name: 'users',
      columns: [
        {name: 'name', type: 'string', isIndexed: true},
        {name: 'password', type: 'string'},
        {name: 'email', type: 'string'},
      ],
    }),
    tableSchema({
      name: 'sites',
      columns: [
        {name: 'site_id', type: 'string', isIndexed: true},
        {name: 'txt', type: 'string'},
        {name: 'image', type: 'string'},
        {name: 'author', type: 'string'},
      ],
    }),
  ],
});

database_users.js

import {Database} from '@nozbe/watermelondb';
import SQLiteAdapter from '@nozbe/watermelondb/adapters/sqlite';

import {User} from './model';
import {shema} from './shema';
import migrations from './migrations';

import seedDatabase from '../seedDatabase';

const adapter = new SQLiteAdapter({
  migrations,
  shema,
  jsi: true, // enable if Platform.OS === 'ios'
});

const database = new Database({
  adapter,
  modelClasses: [User],
});

// Initialize the database
export async function initializeDatabase() {
  try {
    await database.adapter.initialize();
    console.log('Database initialized');
  } catch (error) {
    console.error('Error initializing database:', error);
    throw error; // Propagate the error if initialization fails
  }
}

export default database;

./navigation/components/UserListItem.js

import {Button, StyleSheet, Text, View} from 'react-native';
import {deleteUser} from '../../model/helpers';
import {withObservables} from '@nozbe/watermelondb/react';

function UserListItem({user}) {
  return (
    <View key={user.name} style={styles.userRow}>
      <View style={styles.button}>
        <Button title="DEL" onPress={() => deleteUser(user)} />
        {/* For demo simplicity when we update a record we just update its minPlayers */}
      </View>
      <Text>{user.name} - </Text>
    </View>
  );
}

const enchance = withObservables(['user'], ({user}) => ({
  user,
}));

UserListItem = enchance(UserListItem);
export default UserListItem;

const styles = StyleSheet.create({
  userRow: {flexDirection: 'row', alignItems: 'center', marginVertical: 8},
  button: {marginRight: 8, flexDirection: 'row'},
});

./navigation/screens/DetailsScreen.js

import * as React from "react";
import { View, Text } from "react-native";

export default function DetailsScreen({ navigation }) {
    return (
        <View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
            <Text
                onPress={() => navigation.navigate("Home")}
                style={{ fontSize: 26, fontWeight: "bold" }}
            >
                Culturise
            </Text>
        </View>
    );
}

./navigation/screens/SearchScreen.js

import * as React from 'react';
import {
  StyleSheet,
  KeyboardAvoidingView,
  Platform,
  Keyboard,
} from 'react-native';
import {Searchbar} from 'react-native-paper';

export default function SearchScreen({navigation}) {
  const [searchQuery, setSearchQuery] = React.useState('');
  var [isSearching, setIsSearching] = React.useState(false);

  React.useEffect(() => {
    const keyboardDidShowListener = Keyboard.addListener(
      'keyboardDidShow',
      () => {
        setIsSearching(true);
      },
    );
    const keyboardDidHideListener = Keyboard.addListener(
      'keyboardDidHide',
      () => {
        setIsSearching(false);
      },
    );

    // makni listenere kada nema tipkovnice
    return () => {
      keyboardDidShowListener.remove();
      keyboardDidHideListener.remove();
    };
  }, []);
  return (
    <KeyboardAvoidingView
      style={styles.container}
      behavior={Platform.OS === 'ios' ? 'padding' : 'height'}>
      <Searchbar
        style={isSearching ? styles.bar1 : styles.bar}
        placeholder="Search"
        onChangeText={setSearchQuery}
        value={searchQuery}
        showLoading={true}
      />
    </KeyboardAvoidingView>
  );
}

const styles = StyleSheet.create({
  bar: {
    height: 60,
    position: 'relative',
    width: '90%',
    bottom: '-170%',
    //flex: 1,
    opacity: 1,
    marginRight: 'auto',
    marginLeft: 'auto',
    lineHeight: 44,
    borderTopLeftRadius: 40,
    borderTopRightRadius: 40,
    borderBottomRightRadius: 40,
    borderBottomLeftRadius: 40,
    backgroundColor: 'rgba(255, 255, 255, 1)',
    color: 'rgba(136, 136, 136, 1)',
    fontSize: 30,
    textAlign: 'left',
    fontFamily: 'Roboto',
    borderWidth: 1,
    borderColor: 'rgba(255, 255, 255, 1)',
    borderStyle: 'solid',
  },
  container: {
    flex: 1,
  },
  bar1: {
    height: 60,
    width: '90%',
    bottom: '-3%',
    opacity: 1,
    marginRight: 'auto',
    marginLeft: 'auto',
    lineHeight: 44,
    borderTopLeftRadius: 40,
    borderTopRightRadius: 40,
    borderBottomRightRadius: 40,
    borderBottomLeftRadius: 40,
    backgroundColor: 'rgba(255, 255, 255, 1)',
    color: 'rgba(136, 136, 136, 1)',
    fontSize: 30,
    textAlign: 'left',
    fontFamily: 'Roboto',
    borderWidth: 1,
    borderColor: 'rgba(255, 255, 255, 1)',
    borderStyle: 'solid',
  },
});

./navigation/screens/SettingsScreen.js

import {View, Text} from 'react-native';
import {User} from '../../model/model';

export default function SettingsScreen({navigation}) {
  return (
    <View style={{flex: 1, alignItems: 'center', justifyContent: 'center'}}>
      <Text
        onPress={() => navigation.navigate('Home')}
        style={{fontSize: 26, fontWeight: 'bold'}}>
        {Settings}
      </Text>
    </View>
  );
}

./navigation/screens/HomeScreen.js

import * as React from 'react';
import {View, Text} from 'react-native';
import {withObservables} from '@nozbe/watermelondb/react';

//import {User} from '../../model/model';
import {UserListItem} from '../components/UserListItem';

function HomeScreenComponent({navigation, users}) {
  return (
    <View style={{flex: 1, alignItems: 'center', justifyContent: 'center'}}>
      <Text
        onPress={() => navigation.navigate('Home')}
        style={{fontSize: 26, fontWeight: 'bold'}}>
        {users.map(user => (
          <UserListItem key={user.name} user={user} />
        ))}
      </Text>
    </View>
  );
}

const enhance = withObservables([], ({database}) => ({
  users: database.collections.get(User.table).query().observe(),
}));

const HomeScreen = enhance(HomeScreenComponent);

export default HomeScreen;

./navigation/MainContainer.js

import * as React from 'react';
import {Image} from 'react-native';
import {NavigationContainer} from '@react-navigation/native';
import {createBottomTabNavigator} from '@react-navigation/bottom-tabs';
import Feather from 'react-native-vector-icons/Feather';

// Screens
import HomeScreen from './screens/HomeScreen';
import DetailsScreen from './screens/DetailsScreen';
import SettingsScreen from './screens/SettingsScreen';
import SearchScreen from './screens/SearchScreen';

//Screen names
const homeName = 'Lucky';
const detailsName = 'Culturise';
const settingsName = 'Account';
const searchName = 'Search';

const Tab = createBottomTabNavigator();

function MainContainer() {
  return (
    <NavigationContainer>
      <Tab.Navigator
        initialRouteName={homeName}
        screenOptions={({route}) => ({
          tabBarIcon: ({color, size}) => {
            let iconName;
            let rn = route.name;

            if (rn === homeName) {
              iconName = 'lucky';
            } else if (rn === detailsName) {
              iconName = 'compass';
            } else if (rn === settingsName) {
              iconName = 'user';
            } else if (rn === searchName) {
              iconName = 'search';
            }

            if (iconName != 'lucky') {
              return <Feather name={iconName} size={size} color={color} />;
            } else {
              return (
                <Image
                  source={require('../assets/dice.png')}
                  style={{width: 24, height: 24}}
                />
              );
            }
          },
        })}
        tabBarOptions={{
          activeTintColor: 'black',
          inactiveTintColor: 'grey',
          labelStyle: {paddingBottom: 10, fontSize: 9},
          style: {padding: 10, height: 70},
        }}>
        <Tab.Screen name={homeName} component={HomeScreen} />
        <Tab.Screen name={detailsName} component={DetailsScreen} />
        <Tab.Screen name={searchName} component={SearchScreen} />
        <Tab.Screen name={settingsName} component={SettingsScreen} />
      </Tab.Navigator>
    </NavigationContainer>
  );
}

export default MainContainer;

full log of the error:

 TypeError: Cannot read property 'collections' of undefined

This error is located at:
    in withObservables[] (created by SceneView)
    in StaticContainer
    in EnsureSingleNavigator (created by SceneView)
    in SceneView (created by BottomTabView)
    in RCTView (created by View)
    in View (created by Screen)
    in RCTView (created by View)
    in View (created by Background)
    in Background (created by Screen)
    in Screen (created by BottomTabView)
    in RNSScreen
    in Unknown (created by InnerScreen)
    in Suspender (created by Freeze)
    in Suspense (created by Freeze)
    in Freeze (created by DelayedFreeze)
    in DelayedFreeze (created by InnerScreen)
    in InnerScreen (created by Screen)
    in Screen (created by MaybeScreen)
    in MaybeScreen (created by BottomTabView)
    in RNSScreenContainer (created by ScreenContainer)
    in ScreenContainer (created by MaybeScreenContainer)
    in MaybeScreenContainer (created by BottomTabView)
    in RNCSafeAreaProvider (created by SafeAreaProvider)
    in SafeAreaProvider (created by SafeAreaProviderCompat)
    in SafeAreaProviderCompat (created by BottomTabView)
    in BottomTabView (created by BottomTabNavigator)
    in PreventRemoveProvider (created by NavigationContent)
    in NavigationContent
    in Unknown (created by BottomTabNavigator)
    in BottomTabNavigator (created by MainContainer)
    in EnsureSingleNavigator
    in BaseNavigationContainer
    in ThemeProvider
    in NavigationContainerInner (created by MainContainer)
    in MainContainer (created by App)
    in App
    in RCTView (created by View)
    in View (created by AppContainer)
    in RCTView (created by View)
    in View (created by AppContainer)
    in AppContainer
    in AwesomeProject1(RootComponent), js engine: hermes
 ERROR  TypeError: Cannot read property 'collections' of undefined

This error is located at:
    in withObservables[] (created by SceneView)
    in StaticContainer
    in EnsureSingleNavigator (created by SceneView)
    in SceneView (created by BottomTabView)
    in RCTView (created by View)
    in View (created by Screen)
    in RCTView (created by View)
    in View (created by Background)
    in Background (created by Screen)
    in Screen (created by BottomTabView)
    in RNSScreen
    in Unknown (created by InnerScreen)
    in Suspender (created by Freeze)
    in Suspense (created by Freeze)
    in Freeze (created by DelayedFreeze)
    in DelayedFreeze (created by InnerScreen)
    in InnerScreen (created by Screen)
    in Screen (created by MaybeScreen)
    in MaybeScreen (created by BottomTabView)
    in RNSScreenContainer (created by ScreenContainer)
    in ScreenContainer (created by MaybeScreenContainer)
    in MaybeScreenContainer (created by BottomTabView)
    in RNCSafeAreaProvider (created by SafeAreaProvider)
    in SafeAreaProvider (created by SafeAreaProviderCompat)
    in SafeAreaProviderCompat (created by BottomTabView)
    in BottomTabView (created by BottomTabNavigator)
    in PreventRemoveProvider (created by NavigationContent)
    in NavigationContent
    in Unknown (created by BottomTabNavigator)
    in BottomTabNavigator (created by MainContainer)
    in EnsureSingleNavigator
    in BaseNavigationContainer
    in ThemeProvider
    in NavigationContainerInner (created by MainContainer)
    in MainContainer (created by App)
    in App
    in RCTView (created by View)
    in View (created by AppContainer)
    in RCTView (created by View)
    in View (created by AppContainer)
    in AppContainer
    in AwesomeProject1(RootComponent), js engine: hermes


Latest try: Tried filling the database with seedDatabase function, didn't solve problem


Solution

  • Looking at the error TypeError: Cannot read property 'collections' of undefined gives a good hint to where the issue lies.

    From ./navigation/screens/HomeScreen.js

    const enhance = withObservables([], ({database}) => ({
      users: database.collections.get(User.table).query().observe(),
    }));
    

    The "database" parameter is undefined.

    Notice you are not passing in the database here ./navigation/MainContainer.js

    <Tab.Screen name={homeName} component={HomeScreen} />
    

    WatermelonDB and React Native have a few ways to deal with this. What I have done while using WatermelonDB in the past is prop drilling. If you do it this way, you may need to use this.props.database in some cases, so do be aware of that.

    <Tab.Screen name={homeName} children={ () => <HomeScreen database={ database }/> }/>
    

    You can also use providers. The watermelonDB docs has a section on their database provider. While I recommend reading their docs for more information, some of the code is copied below.

    import { DatabaseProvider } from '@nozbe/watermelondb/react'
    
    render(
      <DatabaseProvider database={database}>
        { // rest of the app that needs the database }
      </DatabaseProvider>
    )
    

    There are two documented ways to consume the database

    import { withDatabase, compose } from '@nozbe/watermelondb/react'
    
    export default compose(
      withDatabase,
      withObservables([], ({ database }) => ({
        blogs: database.get('blogs').query(),
      }),
    )(BlogList)
    
    // Or
    
    import { useDatabase } from '@nozbe/watermelondb/react'
    
    const Component = () => {
       const database = useDatabase()
    }