How to get ref x, y position in functional component in react-native? - scrollview

I want to get the x, y position when the text cursor is focused on TextInput.
how to get ref position?
here is my code
const Code = () => {
const emailInput = useRef();
const scrollViewRef = useRef();
const [email, setEmail] = useState<string>('');
const scrollToEmail = () => {
// I want to scroll the y position of the scroll to the center where the TextInput's cursor is focused.
};
return (
<SafeAreaView style={{ flex: 1}}>
<ScrollView keyboardShouldPersistTaps="handled" ref ={scrollViewRef}>
<KeyboardAvoidingView enabled behavior={'padding'}>
<TextInput
ref={emailInput}
value={email}
returnKeyType="done"
onChangeText={(text) => setEmail(text)}
onFocus = {() => scrollToEmail()} <== function works here!
/>
</KeyboardAvoidingView>
</ScrollView>
</SafeAreaView>
);
};
export default Code;
i tried this
1.
const handler = findNodeHandle(emailInput.current);
console.log(
emailInput.measure(handler, (x, y, width, height) => {
console.log(y);
}),
); <== TypeError: emailInput.measure is not a function
const handler = findNodeHandle(emailInput.current);
console.log(emailInput.current.getBoundingClientRect()); <== TypeError: emailInput.current.getBoundingClientRect is not a function
there is no way get the ref's postition in a functional component?

You can use onLayout prop on the ScrollView to the the height, width, X and Y value.
If you want to use the ref, you have to set or console.log the X and Y value on a useLayoutEffect. This is because you need to wait for the component to be rendered and "drawn" before you get the information, otherwise you will get only 0

Related

Dimesnion from React/native not working properly on Android device

I am trying to create an Instagram like reel functionality in react-native app.
I want to display a video element on entire screen available space.
For the purpose of it I am using a FlatList.
This code doesn't work on every device.
'const HomeScreen = ({navigation}) => {
const dataArray=[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19];
const renderItem=({item,index})=>{
return(
<View style={[{height:Dimesnions.get('window').height-bottomtabBarHeight,borderBottomColor:'black'},]}>
<Text>{item}</Text>
</View>
)
}
return (
<SafeAreaView style={styles.container}>
<StatusBar />
<FlatList
data={dataArray}
renderItem={renderItem}
pagingEnabled
decelerationRate={'fast'}
/>
</SafeAreaView>
)
}
export default HomeScreen
const styles = StyleSheet.create({
container:{
flex:1,
}
})`
work for me
import dimansion from react native
const {height,width} = dimansion.get('window')
const Myfun=()=>{
return(
<View style={{flex:1}}>
<View style={{height:height}}></View>
</View>
)
}
actually what you need to do is make a state variable and assign dimension's value in it and then in useEffect make a listener which would be triggered every time your device rotates or dimensions of device gets changed
example below:
const [dim, setDim] = useState(Dimensions.get('screen');
useEffect(() => {
const subscription = Dimensions.addEventListener(
'change',
({ screen}) => {
setDim({ screen});
},
);
return () => subscription?.remove(); });
you can use dim.height for height and dim.width for width in your desired component
PS. I have made a complete and comprehensive tutorials regarding Dimensions API if you want to check it out then link is below(Tutorial is in Urdu/Hindi)
https://www.youtube.com/watch?v=mISuyh_8-Cs&t=605s&ab_channel=LearnByDill
I think it is because of typo which you used Dimesnions instead of Dimensions. You can get height or width of device as below:
const ScreenWidth = Dimensions.get('window').width;
const ScreenHeight = Dimensions.get('window').height;

why it is not working to remove if it the same color name?

I have a problem
My currentColor goes not to null, If I Press a color I set the color to currentColor, if the same color is again pressed then I remove the color (I set currentColor to null). but its not working
const ModalProductSizeColor = forwardRef<BottomSheetModal, IModalProductSizeColor>(({ onPress }, ref) => {
const navigation = useNavigation<NativeStackNavigationProp<RootStackParams>>();
const [currentColor, setCurrentColor] = useState<ColorsOptions | null>(null);
const [sizeScreen, setSizeScreen] = useState<boolean>(false);
const snapPoints = useMemo(() => ['25%', 250], []);
const handleSetCurrentColor = (color: ColorsOptions) => {
console.log(currentColor + 'current in func');
currentColor === color ? setCurrentColor(null) : setCurrentColor(color)
};
console.log(currentColor);
return (
<BottomSheetModal
ref={ref}
index={1}
snapPoints={snapPoints}
handleIndicatorStyle={[s.handleStyle, s.handleColorWhite]}
backdropComponent={BottomSheetBackdrop}
>
<Text style={s.title}>test</Text>
<Colors
colors={ColorsData}
onPress={handleSetCurrentColor}
onLongPress={(x) => console.log(x)}
containerStyle={s.containerStyle}
/>
<View style={s.btnContainer}>
<Pressable style={ButtonStyles.full}>
<Text style={s.btnText}>Fertig</Text>
</Pressable>
</View>
</BottomSheetModal>
)
});
In my function when I console.log currentColor its null but outside of the function it is setted , I dont check anything anymore I am very thankful for your help
Assuming your Colors component passes the color to your handleSetCurrentColor, you could try to set your currentColor like this
...
console.log(currentColor + 'current in func');
setCurrentColor(prevCurrentColor => prevCurrentColor === color ? null : color)
...

Increase height of a View on swipe up in react native expo

I have a container that contains multiple views like this :
export default function MyComponent() {
<View *** container *** >
<View> // some stuff </View>
<View> // some stuff </View>
<ScrollView> // some stuff </ScrollView>
</View
}
The ScrollView is about 40% of the container's height, in absolute position.
What I need to do is to be able to extend it over the whole screen with a swipe up.
I tried to use some modals npm package but I can't make it work.
A few things:
From my experience, ScrollViews and FlatLists work best when they have a flex of one and are wrapped in a parent container that limits their size.
I couldnt determine if you wanted to wrap the entire screen in a GestureDector and listen to swipes or if you only wanted the ScrollView to listen for scroll events. Because you want the ScrollView to take up the entire screen, I assume you wanted to listen to onScroll events
So here's a demo I put together:
import * as React from 'react';
import {
Text,
View,
Animated,
StyleSheet,
ScrollView,
useWindowDimensions
} from 'react-native';
import Constants from 'expo-constants';
import Box from './components/Box';
import randomColors from './components/colors'
const throttleTime = 200;
// min time between scroll events (in milliseconds)
const scrollEventThrottle = 100;
// min up/down scroll distance to trigger animatino
const scrollYThrottle = 2;
export default function App() {
const scrollViewAnim = React.useRef(new Animated.Value(0)).current;
let lastY = React.useRef(0).current;
// used to throttle scroll events
let lastScrollEvent = React.useRef(Date.now()).current;
const [{ width, height }, setViewDimensions] = React.useState({});
const [isScrollingDown, setIsScrollingDown] = React.useState(false);
const [scrollViewTop, setScrollViewTop] = React.useState(400);
// scroll view is 40% of view height
const defaultHeight = height * .4;
// call onLayout on View before scrollView
const onLastViewLayout = ({nativeEvent})=>{
// combine the y position with the layout height to
// determine where to place scroll view
setScrollViewTop(nativeEvent.layout.y + nativeEvent.layout.height)
}
const onContainerLayout = ({nativeEvent})=>{
// get width and height of parent container
// using this instead of useWindowDimensions allow
// makes the scrollView scale with parentContainer size
setViewDimensions({
width:nativeEvent.layout.width,
height:nativeEvent.layout.height
})
}
//animation style
let animatedStyle = [styles.scrollView,{
height:scrollViewAnim.interpolate({
inputRange:[0,1],
outputRange:[defaultHeight,height]
}),
width:width,
top:scrollViewAnim.interpolate({
inputRange:[0,1],
outputRange:[scrollViewTop,-10]
}),
bottom:60,
left:0,
right:0
}]
const expandScrollView = ()=>{
Animated.timing(scrollViewAnim,{
toValue:1,
duration:200,
useNativeDriver:false
}).start()
}
const shrinkScrollView = ()=>{
Animated.timing(scrollViewAnim,{
toValue:0,
duration:200,
useNativeDriver:false
}).start()
}
const onScroll=(e)=>{
// throttling by time between scroll activations
if(Date.now() - lastScrollEvent <scrollEventThrottle ){
console.log('throttling!')
return
}
lastScrollEvent = Date.now()
// destructure event object
const {nativeEvent:{contentOffset:{x,y}}} = e;
const isAtTop = y <= 0
const isPullingTop = lastY <= 0 && y <= 0
let yDiff = y - lastY
let hasMajorDiff = Math.abs(yDiff) > scrollYThrottle
// throttle if isnt pulling top and scroll dist is small
if(!hasMajorDiff && !isPullingTop ){
return
}
const hasScrolledDown = yDiff > 0
const hasScrolledUp = yDiff < 0
if(hasScrolledDown){
setIsScrollingDown(true);
expandScrollView()
}
if(isAtTop || isPullingTop){
setIsScrollingDown(false)
shrinkScrollView();
}
lastY = y
}
return (
<View style={styles.container} onLayout={onContainerLayout}>
<Box color={randomColors[0]} text="Some text"/>
<Box color={ randomColors[1]} text="Some other text "/>
<View style={styles.lastView}
onLayout={onLastViewLayout}>
<Text>ScrollView Below </Text>
</View>
<Animated.View style={animatedStyle}>
<ScrollView
onScroll={onScroll}
style={{flex:1}}
>
{randomColors.map((color,i)=>
<Box color={color} height={60} text={"Item Number "+(i+1)}/>
)}
</ScrollView>
</Animated.View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
// justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
padding: 8,
},
scrollView:{
// position:'absolute',
position:'absolute',
marginVertical:10,
height:'40%',
backgroundColor:'lightgray'
},
lastView:{
alignItems:'center',
paddingVertical:5,
borderBottomWidth:1,
borderTopWidth:1
}
});
The result is that on downward scrolling, the scrollview expands and takes up the entire screen, and shrinks when the user scrolls to the top.
Edit : I found that simply grabbing the y position and the height of the view directly before the scroll view made it easy to calculate where the position the ScrollView, allowing for the ScrollView to be positioned absolute all the time.
Here is a very basic example of how to use FlatList (similar to ScrollView) and allow for the scrolling behavior you are wanting:
import React from "react";
import {Text,View} from "react-native";
const App = () => {
const myData = {//everything you want rendered in flatlist}
const renderSomeStuff = () => {
return (
<View>
<Text> Some Stuff </Text>
</View>
)
};
const renderOtherStuff = () => {
return (
<View>
<Text> Other Stuff </Text>
</View>
);
};
return (
<View>
<FlatList
data={myData}
keyExtractor={(item) => `${item.id}`}
showsVerticalScrollIndicator
ListHeaderComponent={
<View>
{renderSomeStuff()}
{renderOtherStuff()}
</View>
}
renderItem={({ item }) => (
<View>
<Text>{item}</Text>
</View>
)}
ListFooterComponent={
<View></View>
}
/>
</View>
);
};
export default App;

React native Flatlist not re-rendering on state change

I realize there are a lot of questions and answers about this out there but I am fairly new to react native and most of the answers are dealing with React Components and not hooks. In the following example availableInterests is pulled from a firestore database call. Then we loop through the availableInterests so the user can select the their interests from the Flatlist of interests. Everything works great except the FLatlist does not re-render so the button that is used to select currentInterests never shows the change that an interest has been selected. Does anyone see what I am missing here?
const [availableInterests, setAvailableInterests] = useState([]);
const [currentInterests, setCurrentInterests] = useState([]);
const selectThisInterest = (item) => {
let myInterests = currentInterests;
if(myInterests.includes(item.id)) {
myInterests.pop(item.id);
} else {
myInterests.push(item.id);
}
setCurrentInterests(myInterests);
}
return <View>
<Text style={styles.text}>Select Your Interests:</Text>
<FlatList
data={availableInterests}
keyExtractor={(item, index) => index.toString()}
extraData={currentInterests}
renderItem={({ item, index }) =>
<View key={item.id}>
<Text>{item.title}</Text>
<Text>{item.description}</Text>
<Image
source={{ uri: item.icon }}
style={{ width: 100, height: 100}}
/>
<TouchableOpacity onPress={() => selectThisInterest(item)}>
<Text style={styles.buttonText}>{`${currentInterests.includes(item.id) ? 'UnSelect' : 'Select'}`}</Text>
<Text>{item.id}</Text>
</TouchableOpacity>
</View>
}>
</FlatList>
</View>
put this state below
const [currentInterests, setCurrentInterests] = useState([]);
const [extra, setExtra] = useState(0);
at the end of your function just put this
const selectThisInterest = (item) => {
....
setExtra(extra + 1)
}
I think the mistake is in your selectThisInterest function. When you are updating the currentInterests based on previous value, React doesn't recognises such a change because you are simply assigning myInterests with your currentInterests.
What you want to do is to copy that array and assign it to myInteresets and then update your values to the new copied array. Once the calculation are completed on the new myInteresets array, the setCurrentInterests() will re-render the app because now React recognises there is a change in the state.
To copy the array, you can use,
let myInterests = [...currentInterests];
change your selectThisInterest function to reflect this change,
const selectThisInterest = (item) => {
let myInterests = [...currentInterests];
if(myInterests.includes(item.id)) {
myInterests.pop(item.id);
} else {
myInterests.push(item.id);
}
setCurrentInterests(myInterests);
}

Flatlist ref values like scrollToEnd throw error of its not a function

I m using flatlist for render tabbar and at certain click it need to be scroll to some value so that i m created ref to scrollTo certain value like scrollToEnd.
// ref declaration was here
const containerRef = useRef();
// This flatlist attached ref as containerRef
<Animated.FlatList
data={TABDATA}
ref={containerRef}
horizontal
pagingEnabled
showsHorizontalScrollIndicator={false}
bounces={false}
keyExtractor={(_, ind) => _.id.toString()}
onScroll={Animated.event(
[{nativeEvent: {contentOffset: {x: scrollX}}}],
{useNativeDriver: true},
)}
renderItem={({item: {Component}, index}) => {
return (
<View style={{width: SCREEN_WIDTH}}>
{/* Header */}
<Component />
</View>
);
}}
/>
// When tab is clicked i need to scrollTo certain value
const onPressTab = () => {
console.log('onPrees Tab', containerRef);
containerRef.current.scrollToEnd();
// or
// containerRef.current.scrollToOffset({ offset: some value })
};
Why its throwing scrollToEnd is not function
Any help please