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
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()
}