React-Native: Passing data through a FlatList Selection - javascript

Im currently trying to pass information through an arrow function that gets called when a user selects a row within the flat list. However, I can't seem to figure out how to pass the data to the next screen appropriately.
My current implementation can be seen below:
import ...
class ViewFollowingScreen extends Component {
constructor(props) {
super(props);
this.state = {
loading: false,
allUsers: [],
data: [],
page: 1,
seed: 1,
error: null,
refreshing: false
}
}
static navigationOptions = {...};
searchUsers(searchBarText) {...};
_onPressItem = () => {
/**
HOW DO YOU PASS THE INFORMATION OF THE SELECTED USER TO THE ViewUser SCREEN??
**/
this.props.navigation.navigate('ViewUser')//Needs to include user information
};
componentDidMount() {...};
makeRemoteRequest = () => {...};
renderSeparator = () => {...};
renderHeader = () => {...};
renderFooter = () => {...};
_renderItem = ({item}) => (
<TouchableOpacity onPress={this._onPressItem}>
<ListItem
roundAvatar
title={`${item.name.first[0].toUpperCase() + item.name.first.substr(1,item.name.first.length)} ${item.name.last[0].toUpperCase() + item.name.last.substr(1,item.name.last.length)}`}
subtitle={'Followers: 15' }
avatar={{ uri: item.picture.thumbnail }}
containerStyle={{borderBottomWidth: 0}}
/>
</TouchableOpacity>
);
render() {
return (
<ViewContainer>
<List containerStyle={{borderBottomWidth: 0, borderTopWidth: 0}}>
<FlatList
data={this.state.data}
renderItem={this._renderItem}
keyExtractor={(item) => item.email}
ItemSeparatorComponent={this.renderSeparator}
ListHeaderComponent={this.renderHeader}
ListFooterComponent={this.renderFooter}
/>
</List>
</ViewContainer>
);
}
}
module.exports = ViewFollowingScreen;
I am currently able to navigate to the next screen when a user selects a row in the flatlist, but I can't seem to figure out exactly how to pass the appropriate data between the two.
Please let me know if you need any additional info.. I'd be happy to edit my question accordingly
Any help is greatly appreciated!

You can send the user information through the params when you are navigating to the next screen. Inside your onPressItem function edit as below:
_onPressItem = () => {
this.props.navigation.navigate('ViewUser',{
user: user // your user details
})
};
and then you can get the user detail on the next screen by
this.props.navigation.state.params.user
Edit:
change the _renderItem function to this:
_renderItem = ({item}) => (
<TouchableOpacity onPress={()=>{this._onPressItem(item)}}>
<ListItem
roundAvatar
title={`${item.name.first[0].toUpperCase() + item.name.first.substr(1,item.name.first.length)} ${item.name.last[0].toUpperCase() + item.name.last.substr(1,item.name.last.length)}`}
subtitle={'Followers: 15' }
avatar={{ uri: item.picture.thumbnail }}
containerStyle={{borderBottomWidth: 0}}
/>
</TouchableOpacity>
)
and onPressItem:
_onPressItem = (user) => {
this.props.navigation.navigate('ViewUser',{
user: user //your user details
})
};

Related

React Native Error : Non-serializable values were found in the navigation state. onPress(Function)

I am trying to resolve the error Non-serializable values were found in the navigation state. Alert > params.action[0].onPress (Function) of React Native navigation. I don't think the function is not passed to the param like the error points out, but it kept returning this same error every time I pressed the icon. I'd appreciate any suggestions or comments.
export default function Alert({ route, navigation }) {
const { colors } = useTheme();
const { t } = useTranslation();
const { title, message, action, option, type } = route?.params;
const success = type === "success";
useEffect(() => {
const backHandler = BackHandler.addEventListener(
"hardwareBackPress",
() => !option?.cancelable
);
return () => backHandler.remove();
}, [option?.cancelable]);
const renderButtonFirst = () => {
const firstTitle = action?.[0]?.text ?? t("close");
const onPressNo = action?.[0];
return (
<TouchableOpacity
onPress={() => {
onPressNo?.onPress();
if (option?.cancelable) navigation.goBack();
}}
>
<Text>
{firstTitle}
</Text>
</TouchableOpacity>
);
};
const renderButtonSecond = () => {
const secondTitle = action?.[1]?.text;
const onPressYes = action?.[1];
if (title && onPressYes) {
return (
<TouchableOpacity
onPress={() => {
onPressYes?.onPress();
if (option?.cancelable) navigation.goBack();
}}
>
<Text>
{secondTitle}
</Text>
</TouchableOpacity>
);
}
};
return (
<View>
<Icon name={success ? "check-circle" : "question-circle"} />
</View>
<View>
<Text>
{title}
</Text>
<Text>
{message}
</Text>
</View>
<View >
{renderButtonFirst()}
{renderButtonSecond()}
</View>
</View>
</View>
);
}
And this is the parent component just in case. But this error is from the Alert component as it says.
const onOpen = (type, title, link) => {
Alert.alert({
title: title,
message: `${t("do_you_want_open")} ${title} ?`,
action: [
{
text: t("cancel"),
onPress: () => console.log("Cancel Pressed"),
style: "cancel",
},
{
text: t("done"),
onPress: () => {
switch (type) {
case "web":
Linking.openURL(link);
break;
case "phone":
Linking.openURL("tel://" + link);
break;
case "email":
Linking.openURL("mailto:" + link);
break;
case "address":
Linking.openURL(link);
break;
}
},
},
],
});
};
{product?.website.length > 0 && (
<TouchableOpacity
onPress={() => {
onOpen("web", t("Website"), product?.website);
}}
>
<View>
<Image
source={Images}
/>
</View>
</TouchableOpacity>
)}
UPDATE 4/1
This is the Navigation component just in case;
import AlertScreen from "#screens/Alert";
export default function Navigator() {
...
return (
<AppearanceProvider>
<NavigationContainer theme={theme}>
<RootStack.Screen
name="Alert"
component={AlertScreen}
gestureEnabled: false,
}}
/>
</RootStack.Navigator>
</NavigationContainer>
</AppearanceProvider>
);
}
From the react navigation docs
This can happen if you are passing non-serializable values such as
class instances, functions etc. in params. React Navigation warns you
in this case because this can break other functionality such state
persistence, deep linking etc.
If you don't use state persistence or deep link to the screen which
accepts functions in params, then the warning doesn't affect you and
you can safely ignore it. To ignore the warning, you can use
YellowBox.ignoreWarnings.
If you are using react-native version > 0.63, use:
import { LogBox } from 'react-native';
LogBox.ignoreLogs([ 'Non-serializable values were found in the
navigation state', ]);
I also got bitten by this. You cannot pass non-simple objects to the navigation.
The problem is not "directly" in the code you posted but somewhere else. Either the go-back triggered the problem "once more" or there is somewhere a line like:
navigation.navigate('Alert', { action: {onPress: some_function }, /* rest */ }
In any case, the problem is that action comes from the parameters and is expected to have am onPress function. You cannot serialize a function an thus cannot model it like that.
Solution: Put that logic into a service and the parameters into the route, something like:
export Service {
do(actionDescription: {type: string, payload: any}) {
if (actionDescription.type === 'log') console.log(actionDescription.payload); // you get the idea
}
}
// in Alert
const onPressNo = () => Service.do(action?.[0].action);
// somewhere:
navitation.navigate('Alert', {action: [{action: {type: 'log', payload: 'Cancel Pressed'} /* complete... */]
So, past only simple objects to the navigation route. Use a pseudo-Command pattern, where the command state is passed into the route and the trigger is centralized somewhere else.

How to prevent re-rendering/fetching data in React Class Component?

I'm working in a music app using React Native, In the Home Screen I make a class component contains more than four FlatList and it's Get data from API "it's large data",
So i make a function For that, and put it inside componentDidMount(),
But I notice when I log the data after setState I see it twice Or more in RN-Debugger
So how can i prevent this happen?
because it's Affected in performance :)
here's a snippet of my code
class Home extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
url: '******',
loading: false,
minimal: false,
MiniURL: '',
songName: '',
currentTrackIndex: 0,
isPlaying: true,
};
}
getRecentSongs = async () => {
try {
let response = await API.get('/index');
let {recent_tracks} = response.data.data;
let recent_tunes = [];
recent_tracks.map(track =>
recent_tunes.push({
id: track.id,
name: track.name,
url: this.state.url + track.sounds,
img: this.state.url + track.avatar,
}),
);
let data = response.data.data;
this.setState({data, recent_tunes, loading: true}, () =>
console.log('data', this.state.data),
);
} catch (error) {
console.log(error);
this.setState({error: true});
}
};
componentDidMount() {
this.getRecentSongs();
}
_renderItem = ({item, index}) => {
const {url} = this.state;
return (
<TouchableNativeFeed
key={item.id}
onPress={() => {
this.props.navigation.navigate({
key: 'Player',
routeName: 'Player',
params: {
tunes: this.state.recent_tunes,
currentTrackIndex: index,
},
});
}}
background={TouchableNativeFeedback.Ripple('white')}
delayPressIn={0}
useForeground>
<Card style={styles.card} noShadow={true}>
<FastImage
style={{width: 200, height: 200}}
source={{uri: url + item.avatar}}
resizeMode={FastImage.resizeMode.cover}
style={styles.cardImg}
/>
<Body style={styles.cardItem}>
<View style={styles.radioCardName}>
<View style={styles.cardViewFlex}>
<Text style={styles.text}>{item.name}</Text>
</View>
</View>
</Body>
</Card>
</TouchableNativeFeed>
);
};
render(){
const {data} = this.state;
return(
...
{/* Recent Songs Here*/}
<View style={{marginVertical: 10}}>
<FlatList
horizontal={true}
showsHorizontalScrollIndicator={false}
data={data.recent_tracks}
contentContainerStyle={{flexGrow: 1}}
ListEmptyComponent={<EmptyList />}
keyExtractor={(track, index) => track.id.toString()}
// initialNumToRender={10}
renderItem={this._renderItem}
/>
</View>
...
)
}
}
It's hard to tell from what's been posted, but is it possible that a key on one of the components is changing more often than you're expecting? React will trigger a full re-render if it detects any key changes.
ComponentDidMount will only be executed once and unmounted when it gets deleted. So that means that it is been created twice in some part of your application.
I have encountered a similar problem and it was regarding my navigation library, in my case, I was using react-navigation https://github.com/react-navigation/react-navigation/issues/2599.
So I can suggest checking if something has happened when your component is created and if it is doing it twice. Also, give a double check if your navigation is not doing the same.
Please use React.memo
It will not re-render the component without any relevent data in its props.
eg:
/**Your render item*/
const AddsItem = React.memo(({item, index}) => {
return (
<TouchableNativeFeed
...
...
</TouchableNativeFeed>
);
});
/**Your class*/
class Home extends React.Component {
constructor(props) {
...
}
render(){
const {data} = this.state;
return(
...
...
...
)
}

Can't get Flatlist pull to refresh working

The docs are pretty straight forward but somehow I can not get the pull to refresh working. The data is loaded correctly at the componentDidMount but _refreshis not called when I try to pull down the list. I tried it on a iPhone and Android device. On Android I can't even pull down the list (no rubber effect).
Here is my code:
export default class HomeScreen extends Component {
static navigationOptions = { header: null };
state = { data: [], isLoading: true };
_fetchData = async () => {
const data = [];
try {
const response = await fetch('https://randomuser.me/api/?results=10');
const responseJSON = await response.json();
this.setState({ data: responseJSON.results, isLoading: false });
} catch (error) {
alert('some error');
this.setState({ isLoading: false });
}
};
_refresh = () => {
alert('this is never be shown');
this.setState({ isLoading: true });
this._fetchData();
};
componentDidMount() {
this._fetchData();
}
render() {
if (this.state.isLoading)
return (
<View style={styles.container}>
<ActivityIndicator size="large" color="darkorange" />
</View>
);
return (
<View style={styles.container}>
<FlatList
data={this.state.data}
keyExtractor={item => item.email}
renderItem={({ item }) => (
<FriendListItem
friend={item}
onPress={() =>
this.props.navigation.navigate('FriendsScreen', {
friend: item,
})
}
/>
)}
ItemSeparatorComponent={() => <View style={styles.listSeparator} />}
ListEmptyComponent={() => <Text>empty</Text>}
onRefresh={this._refresh}
refreshing={this.state.isLoading}
/>
</View>
);
}
}
Double check your FlatList import. I'm pretty sure that you imported FlatList from react-native-gesture-handler. If yes then remove it.
FlatList should be imported from react-native like below.
import { FlatList } from 'react-native';
If above is not the case then share with me your StyleSheet.
Let me know if it helps.

ListView is not re-rendering after dataSource has been updated

I am trying to implement a todo app in react-native with features like addTodo, removeTodo, markCompleted todos. After adding todos, when I press on markComplete text, the listView is not re-rendering, if I reload the app it displays expected results. I am using Firebase database to fetch my todos from.
Basically, I am updating a property in my listView datasource when I click on markComplete. Everything is working fine expect the re-rendering of listView whenever I press markComplete or Completed buttons on UI. I have tried a few solutions suggested in related question, I couldnt get it working.
To be more specific: please look at code below comment // When a todo is changed. I am updating my datasource in those lines of code when I changes something in items array.
Below is my code and snapshot of app UI.
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
ListView,
Text,
View,
TouchableHighlight,
TextInput
} from 'react-native';
var Firebase = require('firebase');
class todoApp extends Component{
constructor(props) {
super(props);
var myFirebaseRef = new Firebase('[![enter image description here][1]][1]database URL');
this.itemsRef = myFirebaseRef.child('items');
this.state = {
newTodo: '',
completed: false,
todoSource: new ListView.DataSource({rowHasChanged: (row1, row2) => row1 !== row2})
};
this.handleKey = null;
this.items = [];
} // End of Constructor
componentDidMount() {
// When a todo is added
this.itemsRef.on('child_added', (dataSnapshot) => {
this.items.push({
id: dataSnapshot.key(),
text: dataSnapshot.child("todo").val(),
completedTodo: dataSnapshot.child("completedTodo").val()
});
this.setState({
todoSource: this.state.todoSource.cloneWithRows(this.items)
});
});
// When a todo is removed
this.itemsRef.on('child_removed', (dataSnapshot) => {
this.items = this.items.filter((x) => x.id !== dataSnapshot.key());
this.setState({
todoSource: this.state.todoSource.cloneWithRows(this.items)
});
});
// When a todo is changed
this.itemsRef.on('child_changed', (dataSnapshot) => {
this.items.forEach(function (value) {
if(value["id"] == this.handleKey){
this.items["value"]["completedTodo"]= dataSnapshot.child("completedTodo").val()
}
});
this.setState({
todoSource: this.state.todoSource.cloneWithRows(this.items)
});
});
}
addTodo() {
if (this.state.newTodo !== '') {
this.itemsRef.push({
todo: this.state.newTodo,
completedTodo: this.state.completed,
});
this.setState({
newTodo : ''
});
}
console.log(this.items);
}
removeTodo(rowData) {
this.itemsRef.child(rowData.id).remove();
}
handleCompleted(rowData){
this.handleKey = rowData.id;
if(rowData.completedTodo){
this.itemsRef.child(rowData.id).update({
completedTodo: false
})
}
if(rowData.completedTodo == false){
this.itemsRef.child(rowData.id).update({
completedTodo: true
})
}
}
renderRow(rowData) {
return (
<View>
<View style={styles.row}>
<TouchableHighlight
underlayColor='#dddddd'
onPress={() => this.removeTodo(rowData)}>
<Text style={styles.todoText}>{rowData.text}</Text>
</TouchableHighlight>
<TouchableHighlight underlayColor='#dddddd' onPress={() => this.handleCompleted(rowData)}>
{rowData.completedTodo? <Text style={styles.todoText}>Completed</Text>:<Text style={styles.todoText}>MarkCompleted</Text>}
</TouchableHighlight>
</View>
<View style={styles.separator} />
</View>
);
}
render() {
return (
<View style={styles.appContainer}>
<View style={styles.titleView}>
<Text style={styles.titleText}>
My Todos
</Text>
</View>
<View style={styles.inputcontainer}>
<TextInput style={styles.input} onChangeText={(text) => this.setState({newTodo: text})} value={this.state.newTodo}/>
<TouchableHighlight
style={styles.button}
onPress={() => this.addTodo()}
underlayColor='#dddddd'>
<Text style={styles.btnText}>Add!</Text>
</TouchableHighlight>
</View>
<ListView
dataSource={this.state.todoSource}
renderRow={this.renderRow.bind(this)} />
</View>
);
}
} // Main Class End
Make sure to create new objects instead of updating the properties of existing objects.
If you want to update listView, create new objects instead of updating
the properties of existing objects.
The below code resolved a similar issue on Github.
let newArray = oldArray.slice();
newArray[indexToUpdate] = {
...oldArray[indexToUpdate],
field: newValue,
};
let newDataSource = oldDataSource.cloneWithRows(newArray);
For more detailed explanation, This answer might help you.

React Native CameraRoll.getPhotos API doesn't render the results

Hi I have the following class where I am trying to get the photos from camera roll and display it.
class CameraRollProject extends Component {
constructor(props) {
super(props);
this.state = {
images: []
};
}
componentWillMount() {
const fetchParams = {
first: 25,
};
CameraRoll.getPhotos(fetchParams, this.storeImages, this.logImageError);
}
storeImages(data) {
const assets = data.edges;
const images = assets.map((asset) => asset.node.image);
this.state.images = images;
}
logImageError(err) {
console.log(err);
}
render() {
return (
<ScrollView style={styles.container}>
<View style={styles.imageGrid}>
{ this.state.images.map((image) => <Image style={styles.image} source={{ uri: image.uri }} />) }
</View>
</ScrollView>
);
}
};
export default CameraRollProject;
The issue is my render function is getting called before my CameraRoll.getPhotos promise get resolved. So I don't get any photos.
To solve this issue I changed my program into following
render() {
return CameraRoll.getPhotos(fetchParams, this.storeImages, this.logImageError)
.then(() => {
return (
<ScrollView style={styles.container}>
<View style={styles.imageGrid}>
{ this.state.images.map((image) => <Image style={styles.image} source={{ uri: image.uri }} />) }
</View>
</ScrollView>
);
});
}
However this give me the following error
What can I do in above situation? How can I make sure the render only works after the CameraRoll.getPhotos get resolved.
So I resolved this issue. The main reason for my problem was I was not using CameraRoll.getPhotos properly as a Promise. I was passing incorrect parameter inside the function. To solve this I got rid of the following functions
storeImages(data) {
const assets = data.edges;
const images = assets.map((asset) => asset.node.image);
this.state.images = images;
}
logImageError(err) {
console.log(err);
}
And make my CameraRoll.getPhotos like the following
CameraRoll.getPhotos({first: 5}).then(
(data) =>{
const assets = data.edges
const images = assets.map((asset) => asset.node.image);
this.setState({
isCameraLoaded: true,
images: images
})
},
(error) => {
console.warn(error);
}
);
Here is my complete code to get pictures from CameraRoll in react-native just in case anyone interested
class CameraRollProject extends Component {
constructor(props) {
super(props);
this.state = {
images: [],
isCameraLoaded: false
};
}
componentWillMount() {
CameraRoll.getPhotos({first: 5}).then(
(data) =>{
const assets = data.edges;
const images = assets.map((asset) => asset.node.image);
this.setState({
isCameraLoaded: true,
images: images
})
},
(error) => {
console.warn(error);
}
);
}
render() {
if (!this.state.isCameraLoaded) {
return (
<View>
<Text>Loading ...</Text>
</View>
);
}
return (
<ScrollView style={styles.container}>
<View style={styles.imageGrid}>
{ this.state.images.map((image) => <Image style={styles.image} source={{ uri: image.uri }} />) }
</View>
</ScrollView>
);
}
};
export default CameraRollProject;
I think you should use react-native-image-picker
You have many parameters to retrieve a picture as you wish
selectPhotoTapped() {
const options = {
title: 'Choose a picture',
cancelButtonTitle: 'Back',
takePhotoButtonTitle: 'Take a picture...',
chooseFromLibraryButtonTitle: 'Choose from my pictures..',
quality: 1,
maxWidth: 300,
maxHeight: 300,
allowsEditing: true,
mediaType: 'photo',
storageOptions: {
skipBackup: true
}
}
it is much easier to handle than CameraRollProject, and the documentation is very well explained. for what you would do it suits perfectly. (It works on iOS and Android)
One way to do this would be to use a ListView rather than a Scrollview because you can utilize a datasource. Here is a sample of how you could do this:
constructor(props) {
super(props);
const ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });
this.state = { dataSource: ds.cloneWithRows([]) };
this.loadPhotos();
}
loadPhotos() {
const fetchParams = {
first: 25,
};
CameraRoll.getPhotos(fetchParams).then((data) => {
this.state.dataSource.cloneWithRows(data.edges);
}).catch((e) => {
console.log(e);
});
}
This way, you render an empty list (and a loading state for good UX) and then once the fetch has completed you set the data in the ListView.
If you want to stick with the ScrollView and the mapped images, you would also need some sort of loading state until the photos load. Hope this helps.

Categories

Resources