Why does my list of Realm objects loaded into FlatList using realm-react only render if I use the spread operator? - react-native

import React from "react"
import { initialWindowMetrics, SafeAreaProvider } from "react-native-safe-area-context"
import {FlatList, SafeAreaView, Text, View, } from "react-native";
import {colors} from "./theme";
import {LocationRealm} from "./realm/models/LocationRealm";
import RealmContext from './realm/AppRealm';
const { RealmProvider, useQuery } = RealmContext;
function App() {
return <RealmProvider>
<SafeAreaProvider initialMetrics={initialWindowMetrics}>
<LocationsView/>
</SafeAreaProvider>
</RealmProvider>
}
export const LocationsView = () => {
const locations = useQuery(LocationRealm)
return (
<SafeAreaView style={{flex: 1, height: "100%"}}>
<FlatList
data={[...locations]} // <--- working (shows on UI)
// data={locations} // <-- not working (empty list)
keyExtractor={ (item, index) => `${item.id}` }
renderItem={({item}) => {
{console.log(item)}
return <View style={{height: 100, backgroundColor: colors.tint}}>
<Text>{item.locationName}</Text>
</View>
}}
/>
</SafeAreaView>
)
}
export default App
import Realm from "realm";
export class LocationRealm extends Realm.Object<LocationRealm> {
id!: string;
locationName!: string;
static generate(index: number, name: String) {
return {
id: `${Math.random()}`,
locationName: name,
}
}
static schema: Realm.ObjectSchema = {
name: "LocationRealm",
primaryKey: "id",
properties: {
id: "string",
locationName: "string",
}
}
}
The above code is only rendering LocationRealm objects into the UI from the Realm Database when using [...locations] instead of just locations. In all the demos and example projects I have seen, the spread operator was not needed. I am not getting any error messages or crashes, just an empty FlatList.
"#realm/react": "^0.4.3"
"realm": "^11.4.0"

I'm having the same issue here, ignore that chump above.
Strangely - if you use locations.map() you'll be able to render using the same data set, but FlatList returns an empty UI.
If you use the underlying VirtualizedList component and pass data={locations} you'll also be able to render.
There must be something with the FlatList component itself that's having issues with the realm results from useQuery.

https://github.com/realm/realm-js/issues/5404
The issue is that FlatList from react-native introduced a guard in the _getItemCount function in RN 0.71.2. Here is the commit:
https://github.com/facebook/react-native/commit/d574ea3526e713eae2c6e20c7a68fa66ff4ad7d2
useQuery returns an object of type Realm.Results, which fails the Array.isArray() guard.
You can use patch-package to remove the guard from the FlatList.

Related

Nested Lists using FlatList or SectionList

Heres my problem. I need to use react navigator to enter a new page with the appropriate data from a FLatList or SectionList using route.params.
Normally that would be no problem for me, but I want a more complex list with new lists in the list (Ideally using json in the future). I want to make a List that displays and sort animals into categories in a listed format. When you touch the desired animal you're forwarded to a page displaying info and facts aboat that animal.
The list of data looks like this (its shortend, but this is the template):
const species= [
{
title: 'SpeciesGroup1',
data: [
{
id: 1,
title: 'Species1',
}
],
},
{
title: 'SpeciesGroup2',
data: [
{
id: 1,
title: 'Species2',
}
],
},
];
This is the screen that diplays the data. AppList is a FlatList component. Everything is displayed as I want it. I've also tried using SectionList and that worked too.
import React from 'react';
import {
StyleSheet,
View,
FlatList,
} from 'react-native';
import AppList from '../components/AppList';
import AppText from '../components/AppText';
import Screen from '../components/Screen';
import routes from '../navigation/routes';
function SpeciesListScreen({ navigation }) {
return (
<Screen>
<FlatList
data={ species }
renderItem={({ item }) => (
<View style={ styles.container }>
<AppText textType='header'>{ item.title }</AppText>
<AppList items={item.data} onPress={ () => navigation.navigate( routes.SPECIES, item.data )} numberOfColumns={ 2 } />
</View>
)}
/>
</Screen>
);
}
const styles = StyleSheet.create({
container: {
padding: 20,
}
});
export default SpeciesListScreen;
Until now eveything works and loads as it should. The problem comes when i want to display the information in the SpeciesScreen. For some reason I can't access the info in the data array, in this case "title". The page loads perfectly fine. Just without the data.
The screen showing the species info looks like this:
import React from 'react';
import {
StyleSheet,
View,
FlatList,
} from 'react-native';
import AppText from '../components/AppText';
import Screen from '../components/Screen';
function SpeciesScreen({ route }) {
const animal = route.params;
return (
<Screen>
<AppText textType='header'>{ animal.title }</AppText>
</Screen>
);
}
export default SpeciesScreen;
Does anyone have any tips or solutions?
The way you pass the route params is incorrect. The route params are supposed to be an object, but item.data is an array.
You can correct this as follows.
<AppList items={item.data} onPress={ () => navigation.navigate( routes.SPECIES, { species: item.data} )} numberOfColumns={ 2 } />
You can access them as follows.
const animal = route.params.species[0]
If you know that this will always be just one object, you could do this as a preprocessing and just pass the object to the route params. If you got multiple objects, then you might want to loop over it.

Flatlist undefined is not an object React-native

I am building a simple React-native app with Expo for rating Github repositories and ran into a nasty issue. When I am trying to render a list of the repositories with Flatlist it throws me the following error: undefined is not an object (evaluating 'repository.fullName'); although my code is pretty much identical to the one in React-native docs. Here is the RepositoryList.jsx where the Flatlist is being rendered:
import React from 'react';
import { FlatList, View, StyleSheet } from 'react-native';
import RepositoryItem from './RepositoryItem'
const styles = StyleSheet.create({
separator: {
height: 10,
},
});
const repositories = [
{
id: 'rails.rails',
fullName: 'rails/rails',
description: 'Ruby on Rails',
language: 'Ruby',
forksCount: 18349,
stargazersCount: 45377,
ratingAverage: 100,
reviewCount: 2,
ownerAvatarUrl: 'https://avatars1.githubusercontent.com/u/4223?v=4',
},
{
id: 'reduxjs.redux',
fullName: 'reduxjs/redux',
description: 'Predictable state container for JavaScript apps',
language: 'TypeScript',
forksCount: 13902,
stargazersCount: 52869,
ratingAverage: 0,
reviewCount: 0,
ownerAvatarUrl: 'https://avatars3.githubusercontent.com/u/13142323?v=4',
}
];
const ItemSeparator = () => <View style={styles.separator} />;
const RepositoryList = () => {
return (
<FlatList
data={repositories}
ItemSeparatorComponent={ItemSeparator}
renderItem={({repository}) => <RepositoryItem repository={repository}/> }
/>
);
};
export default RepositoryList
and RepositoryItem.jsx which should be rendered within the Flatlist:
import React from 'react'
import {View, Text, StyleSheet} from 'react-native'
const RepositoryItem = ({repository}) => {
return(
<View style={styles.item}>
<Text>Full name:{repository.fullName}</Text>
<Text>Description:{repository.description}</Text>
<Text>Language:{repository.language}</Text>
<Text>Stars:{repository.stargazersCount}</Text>
<Text>Forks:{repository.forksCount}</Text>
<Text>Reviews:{repository.reviewCount}</Text>
<Text>Rating:{repository.ratingAverage}</Text>
</View>
)
}
styles = StyleSheet.create({
item: {
marginHorizontal: 16,
backgroundColor: 'darkorange'
},
});
export default RepositoryItem
After doing my research I found that a lot of people have run into this issue too, and apparently it persists since 0.59 (my React-native is on 0.62, Windows). Apparently the error is being cause by a babel module '#babel/plugin-proposal-class-properties' and the solution would be deleting this module from .babelrc, according to this Github thread https://github.com/facebook/react-native/issues/24421. The problem is that my babel.config.js is extremely simple, and I don't see how I can exclude this module from being required for babel to work. My babel.config.js:
module.exports = function(api) {
api.cache(true);
return {
presets: ['babel-preset-expo'],
};
};
Perhaps there is a way to exclude it through tweaking babel in node_modules, but this solution seems unlikely. Any help or suggestions regarding this issue will be greatly appreciated!
I think your problem consists in destructuring repository in your renderItem method of the FlatList.
You cannot just destructure whatever you want, you have to destructure item from the Flatlist.
Try this way:
const RepositoryList = () => {
return (
<FlatList
data={repositories}
ItemSeparatorComponent={ItemSeparator}
renderItem={({ item }) => <RepositoryItem repository={item}/> }
/>
);
};
Or, if you really want to
const RepositoryList = () => {
return (
<FlatList
data={repositories}
ItemSeparatorComponent={ItemSeparator}
renderItem={({ item: repository }) => <RepositoryItem repository={repository}/> }
/>
);
};

Unstated store based React Navigation causing warning

I'm using react-navigation and Unstated in my react native project.
I have a situation where I would like use:
this.props.navigation.navigate("App")
after successfully signing in.
Problem is I don't want it done directly from a function assigned to a submit button. I want to navigate based upon a global Unstated store.
However, it means that I would need to use a conditional INSIDE of the Subscribe wrapper. That is what leads to the dreaded Warning: Cannot update during an existing state transition (such as within 'render').
render() {
const { username, password } = this.state;
return (
<Subscribe to={[MainStore]}>
{({ auth: { state, testLogin } }) => {
if (state.isAuthenticated) {
this.props.navigation.navigate("App");
return null;
}
console.log("rendering AuthScreen");
return (
<View style={styles.container}>
<TextInput
label="Username"
onChangeText={this.setUsername}
value={username}
style={styles.input}
/>
<TextInput
label="Password"
onChangeText={this.setPassword}
value={password}
style={styles.input}
/>
{state.error && (
<Text style={styles.error}>{state.error.message}</Text>
)}
<Button
onPress={() => testLogin({ username, password })}
color="#000"
style={styles.button}
>
Sign in!
</Button>
</View>
);
}}
</Subscribe>
);
It works. But what's the correct way to do it?
I don't have access to MainStore outside of Subscribe and therefore outside of render.
I'm not sure about the react-navigation patterns but you could use a wrapper around this component which subscribes to 'MainStore' and pass it down to this component as a prop. That way you'll have access to 'MainStore' outside the render method.
I have since found a better solution.
I created an HOC that I call now on any Component, functional or not, that requires access to the store. That give me access to the store's state and functions all in props. This means, I am free to use the component as it was intended, hooks and all.
Here's what it looks like:
WithUnstated.js
import React, { PureComponent } from "react";
import { Subscribe } from "unstated";
import MainStore from "../store/Main";
const withUnstated = (
WrappedComponent,
Stores = [MainStore],
navigationOptions
) =>
class extends PureComponent {
static navigationOptions = navigationOptions;
render() {
return (
<Subscribe to={Stores}>
{(...stores) => {
const allStores = stores.reduce(
// { ...v } to force the WrappedComponent to rerender
(acc, v) => ({ ...acc, [v.displayName]: { ...v } }),
{}
);
return <WrappedComponent {...allStores} {...this.props} />;
}}
</Subscribe>
);
}
};
export default withUnstated;
Used like so in this Header example:
import React from "react";
import { Text, View } from "react-native";
import styles from "./styles";
import { states } from "../../services/data";
import withUnstated from "../../components/WithUnstated";
import MainStore from "../../store/Main";
const Header = ({
MainStore: {
state: { vehicle }
}
}) => (
<View style={styles.plateInfo}>
<Text style={styles.plateTop}>{vehicle.plate}</Text>
<Text style={styles.plateBottom}>{states[vehicle.state]}</Text>
</View>
);
export default withUnstated(Header, [MainStore]);
So now you don't need to create a million wrapper components for all the times you need your store available outside of your render function.
As, as an added goodie, the HOC accepts an array of stores making it completely plug and play. AND - it works with your navigationOptions!
Just remember to add displayName to your stores (ES-Lint prompts you to anyway).
This is what a simple store looks like:
import { Container } from "unstated";
class NotificationStore extends Container {
state = {
notifications: [],
showNotifications: false
};
displayName = "NotificationStore";
setState = payload => {
console.log("notification store payload: ", payload);
super.setState(payload);
};
setStateProps = payload => this.setState(payload);
}
export default NotificationStore;

How to map state to props on the initial file (App.js or Index.js)?

This is probably something very basic. There is a spinner on my App where the routes and providers are declared. This must be reading the redux store, in particular spinner.visible and map to state so I can hide/show the <Spinner> element.
But as I said...this is the entry file of the app. I know how to map it to props using connect, but looks like I can't use connect/mapStateToProps on my entry file.
This works very good, but I don't think that using a subscribe is the best way. I'd like to make the spinner be capable to read the store directly in an elegant way. Any suggestions ?
import React from 'react'
import {Provider} from 'react-redux'
import {View} from 'react-native'
import {createStore, applyMiddleware} from 'redux'
import Spinner from 'react-native-loading-spinner-overlay'
import ReduxThunk from 'redux-thunk'
import reducers from './reducers'
import Routes from './config/routes'
import {getReady} from './services/registration'
import {setAppInitialLoad, setAppSpinner} from './actions/AppActions'
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
initialized: false,
spinner: {
visible: false,
text: ''
}
}
store.subscribe(() => {
//ToDo: I really hope to find an elegant solition for this.
//Since it' the top level JS file of the app, I can't use
//connect/mapStateToProps to map the props :(
const spinner = store.getState().AppReducer.spinner
if(spinner.visible != this.state.spinner.visible) {
this.setState({
spinner: {
visible: spinner.visible,
text: spinner.text
}
});
}
}
)
}
componentDidMount() {
store.dispatch(setAppSpinner({ visible: true, text: 'Loading...'}))
getReady().then(response => {
store.dispatch(setAppInitialLoad(response.data.data))
store.dispatch(setAppSpinner({ visible: false, text: ''}))
this.setState({initialized: true})
})
}
render() {
if (this.state.initialized) {
return (
<View>
<Provider store={store}>
<Routes/>
</Provider>
<Spinner visible={this.state.spinner.visible} textContent={this.state.spinner.text}
textStyle={{color: '#000'}}/>
</View>
)
} else {
return (
<View style={{backgroundColor: 'yellow', flex: 1}}/>
)
}
}
}
const store = createStore(reducers, {}, applyMiddleware(ReduxThunk))
export default App;
Use can use store variable
(In your code, it here: const store = createStore(reducers, {}, ...)
store variable has some method, you can read at here (https://redux.js.org/basics/store)

can react-native-root-siblings work with react-redux

in a handleClick function, update the rootSiblings like this,
handleClick() { this.progressBar.update( <ProgressBar /> ); }
and in ProgressBar component,
import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import { View } from 'react-native';
const getFinishedWidth = progress => ({ width: progress * totalWidth });
const getUnfinishedWidth = progress => ({ width: (1 - progress) * totalWidth });
function CustomerReassignProgressBar(props) {
const { progress } = props;
return (
<View style={styles.bar}>
<View style={getFinishedWidth(progress)} />
<View style={getUnfinishedWidth(progress)} />
</View> );
}
CustomerReassignProgressBar.propTypes = { progress: PropTypes.number, };
const mapStateToProps = state => ({ progress: state.batchReassignProgress, });
export default connect(mapStateToProps)(ProgressBar);
then, when calling handleClick(), the app crushed, the error is, 'Could not find "store" in either the context or props of "Connect(ProgressBar)". Either wrap the root component in a , or explicitly pass "store" as a prop to "Connect(ProgressBar)".'
if I don't use connect in component, it works well. So, I guess, maybe rootSiblings can not work with react-redux. But does anyone knows this problem?
Upgrade to react-native-root-siblings#4.x
Then
import { setSiblingWrapper } from 'react-native-root-siblings';
import { Provider } from 'react-redux';
const store = xxx;// get your redux store here
// call this before using any root-siblings related code
setSiblingWrapper(sibling => (
<Provider store={store}>{sibling}</Provider>
));