ScrollView RTL in react native - reactjs

The following code is my ScrollView in a react native project:
<ScrollView
ref={(scrollView) => { this._scrollView = scrollView; }}
horizontal={true}
showsHorizontalScrollIndicator={false}
showsVerticalScrollIndicator={false}
directionalLockEnabled={true}
bounces={false}
scrollsToTop={false}
>
Now it moves from left to right, How it could move from right to left in first loading?

For RTL setting you should write your code like the below sample:
import React, { useRef } from 'react';
import { ScrollView, StyleSheet } from 'react-native';
const RTLScrollView = () => {
const scrollRef = useRef();
const scrollToEnd = () => scrollRef.current.scrollToEnd({ animated: false });
return (
<ScrollView
horizontal
ref={scrollRef}
showsHorizontalScrollIndicator={false}
onContentSizeChange={scrollToEnd}
contentContainerStyle={styles.contentContainerStyle}
>
~~~
~~~
~~~
</ScrollView>
);
}
const styles = StyleSheet.create({
contentContainerStyle: {
flexDirection: 'row-reverse'
}
});
export default RTLScrollView;
Hint: I don't use your other ScrollView settings like bounces={false}, If you want, put it in your code, my answer is just a sample.

This is truly an annoying Bug in React Native , ScrollView+RTL=Silly Bug.
Though , there are multiple hacks you can adapt , I did this to overcome the bug :
I reversed the data array I am using.
Used : onContentSizeChange event handler to trigger the
scrollToEnd({ animated: false }) function on ScrollView

you can try invertible-scroll-view that supports horizontal and vertical scroll view

As #SlashArash has mentioned you can use the react-native-invertible-scroll-view.
Here's an example:
import React, { Component } from 'react';
import { View, Text, ScrollView} from 'react-native';
import InvertibleScrollView from 'react-native-invertible-scroll-view';
export default class Demo extends Component {
constructor(props) {
super(props);
this.scrollView = null;
}
render() {
let categories = ['one', 'two'];
categories = categories.map((category, index) => {
return (
<Text>{category}</Text>
)
});
return (
<View style={{
flex: 1,
}}>
<InvertibleScrollView inverted
ref={ref => { this.scrollView = ref }}
onContentSizeChange={() => {
this.scrollView.scrollTo({y: 0, animated: true});
}}
horizontal={true}
showsHorizontalScrollIndicator={false}
>
{categories}
</InvertibleScrollView>
</View>
)
}
}

I had to build an autocomplete with an RTL scrolling. This proved to be tricky because the scroll to end solution caused a lot of flickering. I found the best way to make anything RTL is using the transform style. If you use transform, it is important that you apply the transform to each item in the list as well. Otherwise, you will have mirrored text. This solution also works if you want to invert top/bottom, just change transform scaleY instead of x.
Here is my ScrollView:
const renderList = () => {
return itemList.map(item => (
<Item
key={item.id}
address={item}
style={{ transform: [{ scaleX: -1 }] }}
/>
));
};
....
<ScrollView
contentContainerStyle={styles.scrollViewContentContainer}
horizontal
keyboardShouldPersistTaps="always"
ref={scrollView}
style={styles.scrollView}
>
{renderList()}
</ScrollView
Here is the corresponding ScrollView style:
const styles = StyleSheet.create({
scrollView: {
marginRight: 10,
transform: [{ scaleX: -1 }]
},
scrollViewContentContainer: {
flexGrow: 1,
justifyContent: 'flex-end',
}
});

Related

Native React Images not displaying after mapping over and being placed in image tag

Hi I have the following code which I just currently want to print the images out and will style later. It should be printing a picture as it comes across them in the list and printing them alongside its facedown side.
I have changed the way 'cardImages = []' is used from...
const CardImages = [
{
image: require("../assets/images/Matching_Cards/Level_One/BLUE_CARD.png"),
},
To which I realize is not the proper way but I had to try...
const CardImages = [
{
image: "../assets/images/Matching_Cards/Level_One/BLUE_CARD.png",
},
this appeared to make no difference. There are no errors that I can see in the console log, my list is shuffled and filled with images which I can see from the console.log().
Below is my return method attached to my CardDisplay function which should be iterating through them and displaying them in the elements.
return (
<View>
<Text style={{ textAlign: "center" }}>CARD DISPLAY: {difficulty}</Text>
<View>
{cards.map((card) => (
<View key={card.id}>
<Image
style={GlobalStyles.faceDownCard}
source={require("../assets/images/Matching_Cards/BACKGROUND_CARD.png")}
/>
<Image style={GlobalStyles.faceDownCard} source={card} />
</View>
))}
</View>
</View>
);
Below is the complete structure of my component
import React, { useEffect, useState } from "react";
import { Text, View, Image } from "react-native";
import { GlobalStyles } from "../Styles/GlobalStyles";
const CardImages = [
{
image: require("../assets/images/Matching_Cards/Level_One/BLUE_CARD.png"),
},
{
image: require("../assets/images/Matching_Cards/Level_One/ORANGE_CARD.png"),
},
{
image: require("../assets/images/Matching_Cards/Level_One/PINK_CARD.png"),
},
{
image: require("../assets/images/Matching_Cards/Level_One/RED_CARD.png"),
},
{
image: require("../assets/images/Matching_Cards/Level_One/WHITE_CARD.png"),
},
{
image: require("../assets/images/Matching_Cards/Level_One/YELLOW_CARD.png"),
},
];
// shuffle cards
const CardDisplay = ({ difficulty }) => {
const [cards, setCards] = useState([]);
useEffect(() => {
shuffleCards();
}, []);
const shuffleCards = () => {
const shuffleCards = [...CardImages, ...CardImages]
.sort(() => Math.random() - 0.5)
.map((card) => ({ ...card, id: Math.random() }));
setCards(shuffleCards);
};
console.log(cards);
return (
<View>
<Text style={{ textAlign: "center" }}>CARD DISPLAY: {difficulty}</Text>
<View>
{cards.map((card) => (
<View key={card.id}>
<Image
style={GlobalStyles.faceDownCard}
source={require("../assets/images/Matching_Cards/BACKGROUND_CARD.png")}
/>
<Image style={GlobalStyles.faceUpCard} source={card} />
</View>
))}
</View>
</View>
);
};
export default CardDisplay;
Appreciate all and any help ! :)
Your Images need height / width props to be defined. It's not enough to have then defined in the StyleSheet (if you are, I'm not sure); you have to give them to the Image in those props.
try giving your image component some styling of height and width

How to optimize FlatList in React Native

Can you please tell me how can I optimize this flatlist in react native. I mean how can I do that app will render not the whole list of data but just small part of it for example 10 items, and then when the user will scroll it down it will load more of data from list?
that's the code
import React, {useState, useEffect} from 'react';
import {
SafeAreaView,
StatusBar,
StyleSheet,
Text,
View,
FlatList,
TextInput,
} from 'react-native';
import {newdata} from '../Data/newdata';
const Sample = () => {
const DATA = newdata;
const [searchText, onChangeSearch] = useState('');
const [filteredData, setFilteredData] = useState([]);
useEffect(() => {
const filtered = DATA.filter(item =>
item.title.toLowerCase().includes(searchText.toLowerCase()),
);
if (searchText === '') {
return setFilteredData(DATA);
}
setFilteredData(filtered);
}, [searchText]);
const Item = ({title}) => (
<View style={styles.item}>
<Text style={styles.title}>{title}</Text>
</View>
);
const renderItem = ({item}) => <Item title={item.title} />;
return (
<SafeAreaView style={styles.container}>
<TextInput
style={{
height: 50,
borderColor: '#919191',
borderWidth: 1,
margin: 10,
paddingLeft: 15,
borderRadius: 10,
}}
onChangeText={newText => onChangeSearch(newText)}
placeholder="Axtaris..."
/>
<FlatList
data={filteredData}
renderItem={renderItem}
keyExtractor={(item, index) => item.key}
/>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
marginTop: StatusBar.currentHeight || 0,
marginBottom: 75,
},
item: {
backgroundColor: '#ededed',
padding: 20,
marginVertical: 2,
marginHorizontal: 10,
borderRadius: 20,
},
title: {
fontSize: 20,
},
});
export default Sample;
P.S. newdata has about 42000 of items, and app running very slow. That is the screenshot of app
You can easily achieve that by using the initialNumToRender prop in FlatList component
How many items to render in the initial batch. This should be enough to fill the screen but not much more. Note these items will never be unmounted as part of the windowed rendering in order to improve perceived performance of scroll-to-top actions.
<FlatList
data={filteredData}
renderItem={renderItem}
keyExtractor={item => item.key}
initialNumToRender={10}
/>
Ali,
You have some features inside Flatlist in order to optimize it for example:
maxtorenderperbatch: This controls the amount of items rendered per batch, which is the next chunk of items rendered on every scroll.
Read more here
initialNumToRender: Define precise number of items that would cover the screen for every device. [Read more here][1]
Also, you can use Infinite Scroll, is very useful instead of render
the whole list you can render only amount of items, and when the user
is scrolling to the end the app load more items.
onEndReached: Called once when the scroll position gets within onEndReachedThreshold of the rendered content.
onEndReachedThreshold: How far from the end (in units of visible length of the list) the bottom edge of the list must be from the end
of the content to trigger the onEndReached callback.
Here is an example how to use it.
<FlatList
data={filteredData}
renderItem={renderItem}
keyExtractor={(item, index) => item.key}
onEndReached={ loadMoreItems }
onEndReachedThreshold={ 0.5 }
maxToRenderPerBatch={3}
initialNumToRender={5}
/>
loadMoreItems:
const loadMoreItems = ( ) => {
// Here you logic to render more items, when user scroll to the end
}

React, render different components and maintain scroll position

Suppose you have a horizontal flatlist.
When a user clicks a button in an item, you want to present a view which looks different from the flat-list item you had.
Suppose you implement it like the following
{showDetail ? (
<DetailView onPress={toggleShowDetail} />
) : (
<FlatList
data={data}
renderItem={() => (
<View>
<Button onPress={toggleShowDetail} />{' '}
</View>
)}
/>
)}
Is the scroll position of flatlist maintained when the flatlist is replaced with DetailView and replaced back?
if not, what are the approaches I can take?
I'd like to avoid using modal if possible
edit,
I'm not sure if setting style width=0 would maintain the scroll position when set width=prevSavedWidth .. but definately can try..
import _ from 'lodash'
import React, {useState} from 'react'
import {useDispatch} from 'react-redux'
import {useSelector} from 'react-redux'
import {
Text,
Image,
View,
NativeModules,
NativeEventEmitter,
TouchableOpacity,
FlatList,
} from 'react-native'
const Qnas = props => {
const flatlistRef = React.useRef(null)
const [single, setSingle] = React.useState(false)
let qnas = [
{
title: 'a',
id: 1,
},
{
title: 'b',
id: 2,
},
{
title: 'c',
id: 3,
},
{
title: 'd',
id: 4,
},
{
title: 'e',
},
{
title: 'f',
},
{
title: 'j',
},
]
const toggle = () => {
setSingle(!single)
}
const renderItem = ({item: qna, index}) => {
return (
<View style={{height: 80, width: 200}}>
<Text>{qna.title}</Text>
<TouchableOpacity onPress={toggle}>
<Text>toggle</Text>
</TouchableOpacity>
</View>
)
}
const keyExtractor = (item, index) => {
return `qna-${item.title}-${index}`
}
return (
<View style={{height: 200}}>
{single ? (
<View>
<Text>hello</Text>
<TouchableOpacity onPress={toggle}>
<Text>toggle</Text>
</TouchableOpacity>
</View>
) : (
<FlatList
horizontal
ref={flatlistRef}
data={qnas}
renderItem={renderItem}
keyExtractor={keyExtractor}
contentContainerStyle={{
flexDirection: 'column',
flexWrap: 'wrap',
}}
/>
)}
</View>
)
}
export default Qnas
the questions is not clear, but the scroll position of the page is always maintained , even if you render your components on conditional basis. if you want to make sure that you always scroll to the top of the page when you show the Detail view , you can always do window.scrollTo(0, 0 before showDetail flag as true.
Kindly provide more details for better understanding or share a code pen
you can have a handleScroll method which gets called on FlatList Scroll and saves the srollPosition in state
const handleScroll = event => {
console.log(event);
setScrollPosition(event.nativeEvent.contentOffset.x);
};
<FlatList onScroll={handleScroll} />```
and then in your toggle method have a check on if single is true set the scrollPosition of the FlatList using ref
const toggle = () => {
if(single){
this.flatlistRef.scrollToOffset({ animated: true, offset: scrollPosition }
setSingle(!single);
}
hope this helps

Unexpected token, expected ";" in react native

I am a newbie in react native, I am developing a video app to aid my learning curve. In the code below I have tried all I could to solve the error on the "displayModal" line, but could not. Please can anyone help me with this.
I want on image/video capture it will display on the modal and from the modal i will be able to "Discard", or "Save"(to firebase), or "Share" the image/video.
import React from 'react';
import { View, Text, Image, Modal, TouchableOpacity } from 'react-native';
import { Ionicons } from '#expo/vector-icons';
import styles from './styles';
export default ({captures=[]}) => {
state = {
isVisible: false
}
// hide show modal
displayModal(show){ ------this is where am getting the error
this.setState({isVisible: show})
}
return (
<Modal
transparent={true}
visible={this.state.isVisible}
// style={[styles.bottomToolbar, styles.galleryContainer]}
>
<View style={{backgroundColor: "#000000aa", flex: 1}}>
{captures.map(({ uri }) => (
<View style={styles.galleryImageContainer} key={uri}>
<Image source={{ uri }} style={styles.galleryImage} />
</View>
))}
</View>
<TouchableOpacity style={{justifyContent: 'center', alignItems: 'center'}}>
<Ionicons
name="close-outline"
color="white"
size={20}
onPress={() => {this.displayModal(!this.state.isVisible);}}
/>
<Text>Discard</Text>
</TouchableOpacity>
</Modal>
);
};
click here to see error image
From you code it looks like a functional component, but you are using state as class-based component, that might be the reason you are getting error :
export default ({captures=[]}) => {
state = {
isVisible: false
}
// hide show modal
displayModal(show){ ------this is where am getting the error
this.setState({isVisible: show})
}
Above code block should look like this :
export default ({captures=[]}) => {
const [state,setState] = useState({ isVisible: false })
// hide show modal
const displayModal = (show) => {
setState({isVisible: show})
}
You are mixing functional component with class component.
"this.state" and "this.setState" belong to class components and all the rest belongs to functional components.
Try to change this:
state = {
isVisible: false
}
// hide show modal
displayModal(show){ ------this is where am getting the error
this.setState({isVisible: show})
}
To this:
const [isVisible, setIsVisible] = React.useState(false);
const displayModal = show => setIsVisible(show);
In addition, in the return statement, remove the strings/words "this" and "this.state".
Requested addition:
import React, { useState } from 'react';
import { View, Text, Image, Button, Modal, TouchableOpacity } from 'react-native';
import { Ionicons } from '#expo/vector-icons';
import { storage } from './fbstorage';
import { Camera } from 'expo-camera';
import styles from './styles';
export default ({ captures = [] }) => {
const [isVisible, setIsVisible] = useState(false);
const takePicture = async () => {
const photoData = await Camera.takePictureAsync();
if (!photoData.cancelled) {
uploadImage(photoData.uri, imageName)
.then(() => {
Alert.alert("Success");
})
.catch((error) => {
Alert.alert('Error:', error.message);
});
}
}
const uploadImage = async (uri, imageName) => {
const response = await fetch(uri);
const blob = await response.blob();
var ref = storage().ref().child("images/" + imageName);
return ref.put(blob)
}
return (
<Modal
transparent={true}
visible={isVisible}
// style={[styles.bottomToolbar, styles.galleryContainer]}
>
<View style={{ backgroundColor: "#000000aa", flex: 1 }}>
{captures.map(({ uri }) => (
<View style={styles.galleryImageContainer} key={uri}>
<Image source={{ uri }} style={styles.galleryImage} />
</View>
))}
</View>
<TouchableOpacity
style={{
justifyContent: 'center',
alignItems: 'center',
marginTop: 20,
top: -20
}}
onPress={() => setIsVisible(false)}
>
<Ionicons
name="md-reverse-camera"
color="white"
size={40}
/>
<Text style={{ color: 'white' }}>Discard</Text>
</TouchableOpacity>
<Button
title='Save'
onPress={takePicture}
/>
</Modal>
);
};

Select items and transfer selected items to another page

Hello I am creating an app, and am having difficulties trying to create a way where the user selects multiple images, which will then be passed on to another screen. Could I please get some help on this?
Much will be appreciated.
So, the way my app works is that, the user selects multiple items, then there should be an add button or a save button, that will get the selected items and display them to another screen. The items have a value which are Images, not text. This is purely the reason why I asked the question here because most of the React-Native tutorials include values based on text, rather than Images.
The problem I am having, is trying to figure out a way for the user to select multiple items, and clicking a save button, which will in return transfer all of the "selected items" to another screen to be display there. Much like a viewer.
import React, { Component } from 'react';
import { Text, View, StyleSheet, AppRegistry, FlatList, Image, TouchableOpacity } from 'react-native';
import flatListData from '../database';
class FlatListItem extends Component {
static navigationOptions = ({ navigation }) => ({
title: 'FirstScreen!'
})
render() {
return (
<View style={{
flex: 1,
flexDirection:'column',
}}>
<View style={{
flex: 1,
flexDirection:'row',
}}>
<View style={{
flex: 1,
flexDirection:'column',
height: 100
}}>
<TouchableOpacity onPress={() => this.props.navigation.navigate('SecondScreen')} >
<Image source={{uri: this.props.item.imageUrl}}
style={{width: 100, height: 100, margin: 5}}></Image>
</TouchableOpacity>
</View>
</View>
<View style={{
height: 1,
backgroundColor:'white'
}}>
</View>
</View>
);
}
}
class FirstScreen extends Component {
static navigationOptions = ({ navigation }) => ({
title: 'First Screen'
})
render() {
return (
<View style={{flex: 1, marginTop: 22}}>
<FlatList
data={flatListData}
renderItem={({item, index})=>{
//console.log(`Item = ${JSON.stringify(item)}, index = ${index}`);
return (
<FlatListItem item={item} index={index}>
</FlatListItem>);
}}
>
</FlatList>
</View>
);
}
}
export default example;
const styles = StyleSheet.create({
flatListItem: {
color: 'white',
padding: 10,
fontSize: 16,
}
});
Since you did not provide any sample code, so I will try to suggest a way to handle via pseudocode
You can abstract out the list of images into a centralized helper class, then you render from this helper class for user to select.
Now when user have selected one of the image, you just need to capture the ID or any unique identifier, and pass it to second screen.
On this second screen, just using this ID/unique identifier that you've received and search it from the aforementioned centralized helper class and render it.
Looks like you have two things to figure out;
one is keeping track of what items a user has selected on your image selection screen
sending the data back between screens
Looks like you are most likely using react-navigation based on your example so the simplest solution would be to take advantage of React's state and use react-navigation's parameter passing between screens.
With react-navigation you can use the second argument in navigation.navigate to pass parameters/callbacks to the other screen. So you can navigate to a screen and pass a callback to it as such.
...
this.props.navigation.navigate(
'ItemSelectionScreen',
{ onSubmit: (items) => { /* Do something with items */ } }
)
...
And here is a basic example of a selection screen with some comments to explain how it works.
import React from 'react';
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'grey',
alignItems: 'center',
justifyContent: 'center'
}
});
class ItemSelectionScreen extends React.Component {
constructor(props) {
super(props);
this.onSubmit = () => props.navigation.getParam('onSubmit'); // Keep the passed callback once we have navigated to this screen
this.state = {
selectedItems: [] // Our initial selectedItems (empty)
};
}
handleToggle = (item, isSelected) => {
const { selectedItems } = this.state;
this.setState({ selectedItems: isSelected ? selectedItems.filter(ent => ent !== item) : [...selectedItems, item] }); // Toggle if an item is selected
};
handleSubmitAndExit = () => {
const { onSubmit } = this;
const { selectedItems } = this.state;
onSubmit(selectedItems); // Pass your selectedItems back to the other screen
this.props.navigation.goBack(); // And exit the screen
};
handleExit = () => {
this.props.navigation.goBack(); // Exit the screen without calling onSubmit
};
renderItem = (item, index) => {
const { selectedItems } = this.state;
const isSelected = selectedItems.some(ent => ent === item); // Determine if an item is selected
return (
<TouchableOpacity key={index} onPress={() => this.handleToggle(item, isSelected)}>
<Text>{`${isSelected ? 'X' : 'O'} ${item}`}</Text>
</TouchableOpacity>
);
};
render() {
return (
<View style={styles.container}>
{['item1', 'item2', 'item3'].map(this.renderItem)}
<TouchableOpacity onPress={this.handleSubmitAndExit}>
<Text>Submit and Exit</Text>
</TouchableOpacity>
<TouchableOpacity onPress={this.handleExit}>
<Text>Exit</Text>
</TouchableOpacity>
</View>
);
}
}
export default ItemSelectionScreen;
Good luck and hope this was helpful.

Resources