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}) {
Related
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
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
I'm having problems with passing props and this whole stuff. Apparently i did something wrong maybe someone can tell me what should i fix there.
So these are states and callbacks that i use:
const [isVisible, setIsVisible] = useState(false);
const visibleCallBack = () => {
setIsVisible(false);
};
const [budynekText, setBudynekText] = useState("budynek");
const budynekCallBack = (propFromChild) => {
setBudynekText(propFromChild);
};
isVisible should decide whether to render or not a certain component:
<TouchableOpacity onPress={() => setIsVisible(true)}>
<View style={styles.budynekContainer}>
<Text style={styles.budynekTekst}>{budynekText}</Text>
{isVisible ?
<Rozwijana callBack={visibleCallBack} budCallBack={budynekCallBack}/>
: null}
</View>
Here I'm passing these callback functions as a props to "Rozwijana",
"Rozwijana" looks like that:
export const Rozwijana = ({callBackProp, budCallBackProp}) => {
return (
<View style={styles.rozwijanaPoz}>
<Pusty/>
<Box name={"budynek 1"} callBackProp={callBackProp} budCallBackProp={budCallBackProp}/>
<Box name={"budynek 2"} callBackProp={callBackProp} budCallBackProp={budCallBackProp}/>
<Box name={"budynek 3"} callBackProp={callBackProp} budCallBackProp={budCallBackProp}/>
</View>
);
}
Then i pass these callbacks down to "Box" component:
const Box = ({name, callBackProp, budCallBackProp}) => {
return (
<>
<TouchableHighlight onPress={() => {callBackProp(); budCallBackProp(name);}}>
<View style={styles.inputBox}>
<Text style={styles.testText}>{name}</Text>
</View>
</TouchableHighlight>
</>
);
}
The result is, that when i click first time at "budynek" button it works as expected, it opens my "list" but after that, when i try to click any of "Boxes" nothing happens. Is there something wrong? If so, how do i go about making this work? Thanks
I use redux to dispatch an action it's a trigger 'true/false',
So i have a bottom sheet modal contain 'mini-player' when i open it grow height modal to contain the player controller.
in the player controller when i press to a 'play/pause icon' i dispatch an action to set pause from false to true or opposite
but the problem is when action dispatching it's back modal to the bassed height "mini-player"
Gif
So how can i solve it?
in other words, prevent re-render the modal?
Code
Player controller
<View>
<View style={{paddingHorizontal: 10}}>
<Video
....
paused={this.props.isPause} // redux store
....
/>
</View>
{!this.props.isPause ? (
<Button
onPress={() => this.props.isPauseTrigger(true)}> // redux action
<Icon name="md-pause" style={styles.iconColor} />
</Button>
) : (
<Button
onPress={() => this.props.isPauseTrigger(false)}> // redux action
<Icon name="md-play" style={styles.iconColor} />
</Button>
)}
</View>
Mini-Player Component I used 'react-native-reanimated-bottom-sheet'
const renderHeader = () => {
const animatedBackgroundOpacity = Animated.sub(
1,
animatedHeaderContentOpacity,
);
return (
<View onPress={onHeaderPress} key={'header-container'}>
<Animated.View
intensity={100}
tint={'default'}
style={[
styles.headerContentContainer,
{
opacity: animatedHeaderContentOpacity,
},
]}>
<View>
....
{props.isPause ? ( // Redux State
<TouchableOpacity
onPress={() => {
props.isPauseTrigger(false); // Redux action
console.log('should play?', props.isPause);
}}
style={styles.headerActionButton}>
<Ionicons color="#282A36" name="ios-play" size={30} />
</TouchableOpacity>
) : (
<TouchableOpacity
onPress={() => {
props.isPauseTrigger(true); // Redux action
console.log('should pause?', props.isPause);
}}
style={styles.headerActionButton}>
<Ionicons color="#282A36" name="ios-pause" size={30} />
</TouchableOpacity>
)}
</View>
</Animated.View>
</View>
);
};
const renderContent = () => {
return (
<Player />
);
};
What I tried
When i change paused property in <Video /> to use local state 'inside controller player component', It works well without any issue like in GIF But I want to change the icon when i minimize the bottom player.
so it's not listening to the redux state because I use local state
React Re-renders components based on changes in props or states. You can use componentShouldUpdate lifecycle method to prevent default behavior.
Based on how you handle mini-player open/closed state there could be other solutions as well.
What is your parent component?
How are you controling the open/close of the modal and if it works with state and all you need is the butto n icon play/pause you can lift the state, i.e you dont need to store in redux.
So on parent:
State: player: "playing"
Func updatePlayer(mode) this.setState(player:mode)
And in your modal comp wherever you are done with it and have a func to closeModal() just have this.props.updatePlayer(this.state.playerState)
Which will update state on the parent.
In my react-native mobile app I have written a component called Row in row.js that contains a TouchableOpacity with an onClick() event handler. However when the component is clicked the function doesn't run.
The Row component displays some text about a particular film and should run the handlePress() function when clicked:
const Row = props => (
<TouchableOpacity onClick={() => props.handlePress(props.imdbID)} style={styles.row}>
<Text>Some text</Text>
</TouchableOpacity>
)
In a separate app.js file, the handlepress function has been written and is passed to the Row component as a prop. The imdbID variable is also passed to the component from the film object:
handlePress = imdbID => {
// do something with imdbID
}
<Row handlePress={this.handlePress} {...film} />
Please can someone tell me what I am doing wrong and why the function doesn't run.
If you take a look at the docs, it doesnt have onClick.
You should use onPress.
const Row = props => (
// using onPress
<TouchableOpacity onPress={() => props.handlePress(props.imdbID)} style={styles.row}>
<Text>Some text</Text>
</TouchableOpacity>
)
React-Native doesnt provide onClick functionality , it gives onPress instead , so replace onClick with onPress
const Row = props => (
<TouchableOpacity onPress={() => props.handlePress(props.imdbID)} style={styles.row}>
<Text>Some text</Text>
</TouchableOpacity>
)
hope this helps ,feel free for doubts
Use onPress() instead of of onClick()
improved code
const Row = props => (
<TouchableOpacity onPress={()=> props.handlePress(props.imdbID)} style={styles.row}>
<Text>Some text</Text>
</TouchableOpacity>
)
Use onPress instead of onClick and be sure you are importing touchable from react-native not gesture-handler
React native does not have onClick for TouchableOpacity.
Use onPress instead.