Pass function from one screen to another in react native - javascript

Im trying to pass a function handleNewFavourite (which updates my favouriteList state array) from my HomeScreen to my DetailsScreen via navigation params but Im getting the following error: Non-serializable values were found in the navigation state
How should I pass functions that modified the state between different stack screens?
HomeScreen code:
<FlatList
data={checkCategory()}
renderItem={({item}) => (
<TouchableOpacity
onPress={() =>
navigation.navigate('Details', {
item,
handleNewFavourite,
})
}>
<LessonCard lesson={item} />
</TouchableOpacity>
)}
/>
DetailScreen code:
const LessonDetails = ({lesson, handleNewFavourite}: LessonProps) => {
const [favourite, setFavourite] = useState<boolean>(lesson.favourite);
return (
<LessonDetailsContainer>
<LessonDetailsInfoContainer>
<LessonDetailsCategoryHead>
<LessonDetailsCategory>{lesson.category}</LessonDetailsCategory>
<TouchableOpacity
onPress={() => {
setFavourite(!favourite);
handleNewFavourite(lesson);
}}>
<LessonDetailsFavouriteIcon>
{favourite ? '❤️' : '🤍'}
</LessonDetailsFavouriteIcon>
</TouchableOpacity>
</LessonDetailsCategoryHead>
<LessonDetailsTitle>{lesson.title}</LessonDetailsTitle>
<LessonDetailsAuthor>{lesson?.author}</LessonDetailsAuthor>
</LessonDetailsInfoContainer>
<LessonDetailsCardImage
source={{
uri: lesson.image,
}}
/>
<LessonDetailsContentContainer>
<LessonDetailsDescriptionText>
{lesson.content}
</LessonDetailsDescriptionText>
</LessonDetailsContentContainer>
</LessonDetailsContainer>
);
};
export default LessonDetails;

For situation like this, you should learn global state management. ( Context API - Redux etc. )

I think you are disrupting in the wrong way the parameters passed to DetailScreen it should be something like this:
const LessonDetails = ({route}: LessonProps) => {
const {lesson, handleNewFavourite} = route.params;
// The rest of your component here
}
As the documentation here suggested. But as #omerfarukose mentioned is not a bad idea to use state management in this particular scenario

Related

Modal not updating to new item in array,firebase/react native state

my current issue with my react native app is that when a user wants to open a lesson (from the lessons array with each object being a lesson with a title,description,img url etc)to make it bigger through a modal, its state does not update. What i Mean by this is that the books title,description,and other attributes won't change if you press on a new lesson. What would be the solution to this?
export default function Learn() {
const [modalVisible, setModalVisible] = useState(false);
const [lessons,setLessons] = useState()
useEffect(() => {
async function data() {
try {
let todos = []
const querySnapshot = await getDocs(collection(db, "lessons"));
querySnapshot.forEach((doc) => {
todos.push(doc.data())
});
setLessons(todos)
console.log(lessons)
}
catch(E) {
alert(E)
}
}
data()
}, [])
return (
<View style={learnStyle.maincont}>
<View>
<Text style={{fontSize:28,marginTop:20}}>Courses</Text>
<ScrollView style={{paddingBottom:200}}>
{lessons && lessons.map((doc,key) =>
<>
<Modal
animationType="slide"
transparent={true}
visible={modalVisible}
onRequestClose={() => {
Alert.alert("Modal has been closed.");
setModalVisible(!modalVisible);
}}
>
<View style={styles.centeredView}>
<View style={styles.modalView}>
<Image source={{
uri:doc.imgURL
}} style={{width:"100%",height:300}}/>
<Text style={{fontWeight:"700",fontSize:25}}>{doc.title}</Text>
<Text style={{fontWeight:"700",fontSize:16}}>{doc.desc}</Text>
<Pressable
style={[styles.button, styles.buttonClose]}
onPress={() => setModalVisible(!modalVisible)}
>
<Text style={styles.textStyle}>Hide Modal</Text>
</Pressable>
</View>
</View>
</Modal>
<LessonCard setModalVisible={setModalVisible} title={doc.title} desc={doc.desc} img1={doc.imgURL} modalVisible={modalVisible}/>
</>
)}
<View style={{height:600,width:"100%"}}></View>
</ScrollView>
</View>
</View>
)
}
What it looks like:
**image 1 is before you press the modal and the 2nd one is after
**the main issue though is that if you press cancel and press on another lesson the modal that opens has the the same state(title,imgurl,anddesc) as the first lesson and does not change.
The problem is that you create a lot of modal windows through the map function, I suggest making one window and passing the key as a parameter and using it to search for a specific array of data that is shown to the user (photo, title, etc.)
The problem is that all 3 Modals are controlled by the one state variable. So when the code sets modalVisible to true, all 3 modals are being opened at once.
You can fix this in a few ways, but a simple way would be to move the Modal and its state into the LessonCard component. This way each modal will have its own state that's only opened by its card. So the loop in Learn will just be:
{lessons && lessons.map((doc,key) => (
<LessonCard lesson={doc} key={key} />
)}
Adding to address question in comments
LessonCard should not accept setModalVisible or modalVisible props. The
const [modalVisible, setModalVisible] = useState(false);
should be inside LessonCard, not Learn. That way each Card/Modal pair will have its own state.
Additionally, although React wants you to pass the key into LessonCard in the map function, LessonCard should not actually use the key prop for anything. See https://reactjs.org/docs/lists-and-keys.html#extracting-components-with-keys
So, the LessonCard declaration should just be something like
export default function LessonCard({lesson}) {

Each child in a list should have a unique "key" prop, I am getting this error in my react native app

I am getting the below errors when I tried to fetch array data from firebase firestore.
How can I solve this issue could someone please suggest me the code structure.
VM22 bundle.js:3927 Warning: Each child in a list should have a unique "key" prop.
Check the render method of CellRenderer. See https://reactjs.org/link/warning-keys for more information.
at http://localhost:19006/static/js/bundle.js:157320:19
at CellRenderer (http://localhost:19006/static/js/bundle.js:172017:36)
import React, { useState, useEffect } from 'react';
import {View, Button, Text, FlatList, StyleSheet, Pressable, TouchableOpacity} from 'react-native'
import {firebase} from '../config';
const Testing = ({ navigation }) =>{
const [users, setUsers] = useState([]);
const todoRef = firebase.firestore().collection('testing');
useEffect(() => {
todoRef.onSnapshot(
querySnapshot => {
const users = []
querySnapshot.forEach((doc) => {
const { ArrayTesting
} = doc.data()
users.push({
id: doc.id,
ArrayTesting
})
})
setUsers(users)
}
)
}, [])
return (
<View style={{ flex:1,}}>
<FlatList
data={users}
numColumns={1}
renderItem={({item}) => (
<Pressable >
<View>
<View>
{ (item.ArrayTesting)
? item.ArrayTesting.map((item) => <Text>{item}</Text>)
: <Text></Text>
}
</View>
</View>
</Pressable>
)} />
</View>
);}
export default Testing;
While you are mapping components in react, you have to provide unique key for the components, so it will know which component to rerender.
<View>
{ (item.ArrayTesting)
// if item has unique field, please provide that as key in Text
? item.ArrayTesting.map((item,index) => <Text key={index}>{item}</Text>)
: <Text></Text>
}
</View>
Hey flatlist already has a property called keyExtractor , please use that , so that all elements already have an unique key and you dont have to pass any explicitly to child comps :
<Flatlist
keyExtractor={(item, _index) => String(_index)}
/>
And also in your renderItem add the below :
{ (item.ArrayTesting)
? item.ArrayTesting.map((item,index) => <Text key={index}>{item}</Text>)
: <Text></Text>
}
Hope it helps, feel free for doubts
The above answer is correct, however, I'd like to add a point of clarity that the answer above even touched on a bit. I'd like to emphasize it a bit more :).
When adding a key prop to each element (so that react knows how to keep track of them in the DOM), we should provide each <Text /> component with a proper unique key.
This could be like an id: 1 property returned on each object or data point from the server. This will ensure headaches of React misunderstanding your data since your keys are just ordered indexes. If ever your data changes (you update your list to remove one item) React might remove the wrong item do to shifting indexes.
<View>
{(item.ArrayTesting)
// if item has unique field, please provide that as key in Text
? item.ArrayTesting.map((item) => <Text key={item.id}>{item}</Text>)
: <Text></Text>
}
</View>

How to implement custom component that accepts a nested component?

I would like to implement a custom component that accepts a nested component. Something that can be used like this.
<MyCustomComponent>
<AnyNestedComponent/>
</MyCustomComponent>
I searched about this but only found the use of this.props, which is not what I expect.
How do I implement MyCustomComponent in React Native version 0.68?
Note: MyCustomComponent will consist of View(s).
Its fairly simple in RN,
Your customComponent should be like =
const CumstomComp = ({props = {}, children = null}) => {
return(
<View style={{backgroundColor:"red"}} >
{children}
</View>
)
}
and then you use it like this
App.js or whatever file
const App = () => {
return(
<View>
<CustomComp>
<Flatlist />
<View />
</CustomComp>
</View>
)
}
Hope it helps. feel free for doubts

React Native: How to properly disable touchable feedback when performing swiping gestures?

Swiping gestures i.e. scrolling, pull to refresh, etc.
I'm pulling my hair out trying to make this work, I don't understand how apps like Facebook accomplish this so well. It seems like such a simple thing to implement but I cannot for the life of me figure it out.
FYI: I'm using a FlatList with Touchable components inside. I've been messing around with the FlatList props (on scroll, on scroll begin drag, on scroll end drag, etc) as well as the Touchable props (on press, on press in, on press delay in, etc).
What I want: On the Facebook app, the MOMENT I begin scrolling or pulling to refresh, tap feedback is disabled so it doesn't look like I clicked on a post. But at the same time the MOMENT I tap on a post, the tap feedback is super responsive. How is this done?
What I get: The moment I begin scrolling or pulling to refresh, the tap feedback is played even though I wanted to scroll/refresh. To fix this, I tried putting a pressDelayIn of 50ms. But now, quickly tapping on a post doesn't play the feedback.
App.js
export default function App() {
const [refreshing, setRefreshing] = useState(false);
const [posts, setPosts] = useState([
{
id: 1,
username: '#somedude',
body: 'This is the best app ever, wow.',
},
{
id: 2,
username: '#doggo',
body: 'Woof woof. Woof woof woof! Woof... Woof woof? Woof!',
},
]);
const onRefresh = () => {
setRefreshing(true);
setTimeout(() => setRefreshing(false), 1000);
}
return (
<SafeAreaView style={styles.container}>
<FlatList
data={posts}
renderItem={({ item }) => <Post post={item} />}
keyExtractor={item => item.id}
refreshing={refreshing}
onRefresh={onRefresh}
/>
</SafeAreaView>
);
}
Post.js
export const Post = ({ post }) => {
return (
<TouchableOpacity
activeOpacity={0.5}
onPress={() => console.log(`Press id ${post.id}`)}
>
<View style={styles.postPontainer}>
<View style={{ marginBottom: 5 }}>
<Text>{post.username}</Text>
</View>
<View style={styles.textContainer}>
<Text>{post.body}</Text>
</View>
</View>
</TouchableOpacity>
);
}
I definitely understand the frustration. I'd +1 ucup's suggestion to checkout react-native-gesture-handler. In the mean time, I disabled the TouchableOpacity while scrolling, and dialed back the delayPressIn and it seem to work pretty well. See what you think:
Add state to track canPress
export default function App() {
const [canPress, setCanPress] = useState(true); //
const [refreshing, setRefreshing] = useState(false);
...
Wire up to the FlatList
<SafeAreaView style={styles.container}>
<FlatList
data={posts}
onScrollBeginDrag={() => setCanPress(false)} //
onScrollEndDrag={() => setCanPress(true)} //
renderItem={({item}) => <Post post={item} canPress={canPress} />} //
keyExtractor={item => item.id}
refreshing={refreshing}
onRefresh={onRefresh}
/>
</SafeAreaView>
Then just wire up to your TouchableOpacity
<TouchableOpacity
disabled={!canPress} //
activeOpacity={0.5}
delayPressIn={50} //
onPress={() => console.log(`Press id ${post.id}`)}>
...
Good luck!
you can check this answer i think it will help you to resolve your problem.
I think pointerEvents is the property you need to handle this.
this property controls whether the View can be the target of touch events.
Reference 1
Reference 2

React Native, Redux and Navigation: handling state updates when passing params from navigation

I have an app with a component which maps an array of posts (records, rows or whatever you like) from a redux slice into buttons to navigate to a detail component, the navigate action passes the post param so the detail component receives it.
// ParentComponent.js
// Redux selector
const posts = useSelector((state) => state.posts);
// The array is mapped like this
posts.map((post, index) => {
return (
<TouchableOpacity
onPress={() => {
navigation.navigate('TabNavigation', {
screen: 'PostEditor',
params: {post},
});
}}
key={Post.slug}
<PostDetail post={post} />
</TouchableOpacity>
);
});
A post view is composed by a large form, so I had to make the detail component divided into sections (child components), this means I'm passing down the received param to the child components:
// Detail View / PostEditor
const {post} = route.params;
return (
<SafeAreaView>
<ScrollView>
<View style={styles.block}>
<PostFormOne post={post} />
<PostFormTwo post={post} />
<PostFormThree post={post} />
<PostFormFour post={post} />
</View>
</ScrollView>
</SafeAreaView>
);
The problem is when I make a change to the redux store on every child component, the other components are not updated, I guess because the child components are referencing the navigation param post and not the redux post store.
If so, then my question is how would you reference the redux store using the navigation param?
If this is not correct then what would be a better approach to this functionality?
Thanks in advance.
you create another store variable for the selected post along with an action that updates the current post. Then in your TouchableOpacity onPress, you call the action to update the current post.
<TouchableOpacity
onPress={() => {
navigation.navigate('TabNavigation', {
screen: 'PostEditor',
});
dispatch(updateCurrentPost(post));
}}
key={Post.slug}
<PostDetail post={post} />
</TouchableOpacity>
This way, you don't need to pass the post at all, and just do:
useSelector((state)=> state.currentPost);
in all of your sub components. You will need to call the updateCurrentPost action in all your sub components to keep the current post in sync.

Categories

Resources