I have the following simple component:
function TestComp() {
const [selection, setselection] = React.useState({ start: 0, end: 0 });
return (
<View style={{ justifyContent: "center", flex: 1 }}>
<TextInput
selection={selection}
onSelectionChange={(event) => {
const {nativeEvent: { selection: { start, end } }} = event;
setselection({ start, end });
}}
multiline={true}
/>
</View>
);
}
My problem is that there is often a delay with the update of the value of selection through setselection, which causes the caret to jump around or trigger the error: setSpan(1 ... 1) ends beyond length 0
(Which I believe means that the selection is set to be bigger than the TextInput value)
How am I supposed to use the selection prop? My goal is to be able to move the cursor around when I need.
I am using expo, but with remote debugging off to not cause additional lag.
Jumping example:
I think this might be helpful for you.
const Test = () => {
const [style, setStyle] = useState({
TestComponent: {
backgroundColor: "white",
height: 40,
borderWidth: 1,
},
});
const [selection, setSelection] = useState({
start: 0,
end: 0,
});
const [txt, setTxt] = useState("");
return (
<TextInput
style={style.TestComponent}
selection={selection}
value={txt}
multiline={true}
onChangeText={(changedTxt) => {
setTxt(changedTxt);
console.log("onChangeText", selection);
}}
onSelectionChange={(syntheticEvent) => {
const { nativeEvent } = syntheticEvent;
const { selection } = nativeEvent;
console.log("onSelectionChange", selection);
setSelection(selection);
}}
/>
);
};
Related
I want to make a flatlist with three items and the selected/current Index item should be bigger as the two items next to.
Current Code:
const translateX = useSharedValue(0);
const scrollHandler = useAnimatedScrollHandler({
onScroll: (e) => {
translateX.value = e.contentOffset.x;
}
})
const animatedStyle = useAnimatedStyle(() => {
const scale = interpolate(translateX.value, [width / 3, 0, width / 3], [0.2, 1, 0.2], Extrapolate.CLAMP);
return {
transform: [{
scale
}]
}
}, []);
const renderI = ({ item }) => (
<Animated.View style={[animatedStyle]}>
<ProductCardAuto
{...item}
onPressProduct={() => handleNavigateToProduct(item)}
onPressReviews={handleOpenModalReviews}
/>
</Animated.View>
)
<Animated.FlatList
data={mockProducts}
renderItem={renderI}
keyExtractor={(item) => item.id}
snapToAlignment="start"
decelerationRate={"normal"}
onScroll={scrollHandler}
horizontal
snapToInterval={120}
/>
I am very thankful for your help. Idk what I am doing wrong. Maybe you can tell me what is wrong with it. it does not work its scale all items to 0.2 if I scroll
Even if the question has been already answered, I would suggest another approach.
What worked for me is react-native-reanimated-carousel, which not only that is very easy to use and very customizable, but is also using react-native-reanimated and that means native performance and precise animations because it runs the animations on the UI thread rather than the JS thread.
EDIT: I noticed that you're actually looking for a reanimated answer, which is great!
Based on the library I showed you, I believe you're looking for a parallax horizontal.
Here's a link to the example: https://github.com/dohooo/react-native-reanimated-carousel/blob/main/exampleExpo/src/pages/parallax/index.tsx
kindof tried to make it acc to what you needed.
You can check this expo : https://snack.expo.dev/d_myr1HdyN
import React,{useState,useEffect,useRef,useCallback} from 'react';
import { Text, View, StyleSheet ,Animated,FlatList,Dimensions,Easing } from 'react-native';
import Constants from 'expo-constants';
// You can import from local files
import AssetExample from './components/AssetExample';
// or any pure javascript modules available in npm
import { Card } from 'react-native-paper';
const data = ["hey there","indian","mango","whatsup","arsenal","jojoba"]
const screenWidth = Dimensions.get("window").width;
const EachItem = (props) => {
const {text = "",index=0,currentView={}} = props || {}
const {visible = false, index:visibleIndex = 0} = currentView;
console.log(visible,visibleIndex,"wuhyyyyy")
const animRef = useRef(new Animated.Value(0)).current
const startAnimation = useCallback(() => {
Animated.timing(animRef,{
toValue:1,
duration:1000,
useNativeDriver:true,
easing:Easing.ease,
}).start()
},[animRef])
const stopAnim = useCallback(() => {
Animated.timing(animRef,{
toValue:0,
duration:1000,
useNativeDriver:true,
easing:Easing.ease,
}).start()
},[animRef])
useEffect(() => {
if(visible === true && visibleIndex===index ){
startAnimation()
}
if(visible === false ){
setTimeout(() =>stopAnim(),1000)
}
},[startAnimation,visible,visibleIndex,index,stopAnim])
const scaleInter = animRef.interpolate({inputRange:[0,1],outputRange:[1,1.5]})
return(
<Animated.View style={{marginHorizontal:20 ,height:100,
backgroundColor:'rgba(102,116,255,0.6)',
width:screenWidth/3,
alignItems:'center',
justifyContent:'center',
transform:[{
scale:scaleInter
}]
}} >
<Text>{text}</Text>
</Animated.View>
)
}
export default function App() {
const [currentView,setCurrentView] = useState({visible:false,index:null})
const onViewRef = React.useRef((viewableItems) => {
console.log(viewableItems?.viewableItems,"viewableItems")
if(viewableItems?.viewableItems.length === 2){
setCurrentView({visible:false,index:null})
}
if(viewableItems?.viewableItems.length === 1){
const currentItemIndex = viewableItems?.viewableItems[0]?.index
setCurrentView({visible:true,index:currentItemIndex})
}
});
const viewConfigRef = React.useRef({ viewAreaCoveragePercentThreshold: 30 });
const eachItem = ({item,index}) => {
return <EachItem text={item} currentView={currentView} index={index} />
}
return (
<View style={styles.container}>
<Animated.FlatList
data={data}
style={{flex:1}}
contentContainerStyle={{paddingTop:100}}
horizontal={true}
onViewableItemsChanged={onViewRef.current}
viewabilityConfig={viewConfigRef.current}
renderItem={eachItem}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
// justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
// alignItems:'center'
},
paragraph: {
margin: 24,
fontSize: 18,
fontWeight: 'bold',
textAlign: 'center',
},
});
Hope it helps. feel free for doubts
I currently have this relatively simply screen in React, that I need to test in Jest, however, I'm not terribly familiar with the library and this is what I've got so far.
Things I'd like to test?
The onLayoutEvent. Does this need mocked?
Showing / hiding of the spinner on page. At the moment, it finds it here: (which is fine)
expect(spinner).not.toBeNull();
But still finds it after the event call, whereas in actual fact it is hidden by state after the event fires.
Loading for this component occurs from a call to setLoading in it's children. I've also zero clue on how to test this. Any assistance on what / how to test this component appreciated.
describe('Login', () => {
test('it should render', () => {
renderWithStore(<LoginStarter />);
});
test('it should fire a layout event', async () => {
const { getByTestId } = renderWithStore(<LoginStarter />);
const view = await getByTestId('loginId');
const spinner = await getByTestId('loginSpinner');
expect(spinner).not.toBeNull();
act(() =>
fireEvent(view, 'onLayout', {
nativeEvent: { layout: { width: 500 } },
}),
);
});
});
The core component
const [widthLoading, setWidthLoading] = useState(true);
const [loading, setLoading] = useState(false);
const theme = useTheme();
const [containerWidth, setContainerWidth] = useState<number>();
const onLayout = (event: LayoutChangeEvent) => {
const { width } = event.nativeEvent.layout;
setContainerWidth(width);
setWidthLoading(false);
};
const tabs = [
{
title: 'Login',
component: () => LoginScreen({ componentId: props.componentId, setLoading }),
testID: 'tab1',
},
{
title: 'Sign up',
component: () => RegisterScreen({ componentId: props.componentId, setLoading }),
testID: 'tab2',
},
];
const wrapperStyle = {
flex: 1,
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
opacity: loading ? 0.5 : 1,
};
const loadingStyle = {
alignItems: 'center',
justifyContent: 'center',
flex: 1,
position: 'absolute',
};
return (
<View testID="loginId" onLayout={onLayout} style={wrapperStyle as StyleProp<ViewStyle>}>
{loading && (
<View style={loadingStyle as StyleProp<ViewStyle>}>
<ActivityIndicator size="large" color={theme.spinner} />
</View>
)}
<View>
{!widthLoading && containerWidth ? (
<Tabs style="light" tabWidth={Math.round(containerWidth / 2)} tabs={tabs} tabsScrollEnabled />
) : (
<ActivityIndicator testID="loginSpinner" size="large" color={theme.spinner} />
)}
</View>
</View>
);
I'm struggling with testing onLayout as well. One thing I will point out is that in your code
act(() =>
fireEvent(view, 'onLayout', {
nativeEvent: { layout: { width: 500 } },
}),
);
onLayout should actually just be layout. I dug into the library code and found this:
const toEventHandlerName = eventName => `on${eventName.charAt(0).toUpperCase()}${eventName.slice(1)}`;
Also, you don't need to wrap fireEvent with act as it is wrapped by act by default. As per the docs:
Act: Useful function to help testing components that use hooks API. By default any render, update, fireEvent, and waitFor calls are wrapped by this function, so there is no need to wrap it manually.
All that said, I still can't get onLayout to fire.
Edit:
The above actually was triggering the onLayout for me. It was the was I was spying on useState that was my issue.
I'm creating a carousel component in React Native using a FlatList and I use useState hook to control the index of image, images load properly and the problem is I cant use my buttons to control the carousel. for example when I tap on right arrow first time doesn't work but when I tap again it goes to next image.
here's my code:
const { width: windowWidth, height: windowHeight } = Dimensions.get("window");
const slideList = Array.from({ length: 5 }).map((_, i) => {
return {
id: i.toString(),
image: `https://picsum.photos/1440/2842?random=${i}`,
};
});
const Carousel = () => {
const [current, setCurrent] = useState(0);
const length = slideList.length;
const flatListRef = useRef();
const renderItem = ({ item }) => {
const arr = Object.values( item );
return (
<View style={styles.imagesContainer}>
<Image style={styles.image} source={{ uri: item.image }} />
</View>
);
}
const goNextSlide = () => {
setCurrent(current < length -1 ? current + 1 : 0);
flatListRef.current.scrollToIndex({ index: current, animated: true });
};
const goPrevSlide = () => {
setCurrent(current <= length - 1 && current >= 0 ? current -1 : 0);
flatListRef.current.scrollToIndex({ index: current, animated: true });
};
console.log(current)
return (
<View style={styles.screen}>
<View style={styles.controls}>
<TouchableOpacity style={styles.controlleft} onPress={goPrevSlide}>
<CarouselLeftArrow style={styles.leftArrow} size={28} fill='black' />
</TouchableOpacity>
<TouchableOpacity style={styles.controlRight} onPress={goNextSlide}>
<CarouselRightArrow style={styles.rightArrow} size={28} fill='black' />
</TouchableOpacity>
</View>
<FlatList
data={slideList}
keyExtractor={item => item.id}
renderItem={renderItem}
horizontal={true}
showsHorizontalScrollIndicator={false}
pagingEnabled={true}
ref={flatListRef}
/>
</View>
)
}
const styles = StyleSheet.create({
imagesContainer: {
width: windowWidth,
height: 250
},
image: {
width: '100%',
height: '100%'
},
controls: {
backgroundColor: 'yellow',
flexDirection: 'row',
justifyContent: 'space-between',
position: 'absolute',
zIndex: 2,
width: '100%',
top: 100
},
controlLeft: {
},
controlRight: {
}
})
export default Carousel;
any help would be appreciated.
goPrevSlide
setCurrent(current <= length - 1 && current >= 0 ? current -1 : 0);
When current >= 0 is not correct because if current equals zero then you set -1 to current in this case. Replace statement like setCurrent(current ? current - 1 : length - 1);
Since updating state is an async action, you can not handle updated variable immediately, you need to use effect hook in order to catch it.
useEffect(() => {
// fires every time when "current" is updated
flatListRef.current.scrollToIndex({ index: current, animated: true });
}, [current]);
Remove setCurrent function from both handler
try to give width and height to the images, you need that if source is uri.
see you code working at snack (without buttons)
When I search for a name nothing appears and everything gets deleted. Also, when I delete what I typed nothing appears again.
This is the search part I hope it's understandable!
const handleSearch = text => {
const formattedQuery = text.toLowerCase();
const filteredData = filter(fullData, user => {
return contains(user, formattedQuery);
});
setData(filteredData);
setQuery(text);
};
I think the problem lays here but I don't know what to change exactly
const contains = ({ name }, query) => {
const { first, last } = name;
if (first.includes(query) || last.includes(query) ) {
return true;
}
return false;
};
return (
<View
style={{
backgroundColor: '#fff',
padding: 10,
marginVertical: 10,
borderRadius: 20
}}
>
<TextInput
autoCapitalize="none"
autoCorrect={false}
clearButtonMode="always"
value={query}
onChangeText={queryText => handleSearch(queryText)}
placeholder="Search"
style={{ backgroundColor: '#fff', paddingHorizontal: 20 }}
/>
I think you haven't done filtering correctly.
const handleSearch = text => {
const formattedQuery = text.toLowerCase();
const filteredData = fullData.filter(user => contains(user, formattedQuery));
setData(filteredData);
setQuery(text);
};
here is what i'm doing
here are those circle component
const Item = (props) => {
// console.log(props)
const [count, setCount] = useState(0);
console.log('aaa')
return (
<TouchableOpacity style={{ width: containerSize, height: containerSize, padding: PADDING, justifyContent: "center", alignItems: "center" }}
onPress={() => {
count == 0 ? setCount(1) : setCount(0)
}}
>
<View style={[count == 0 ? { backgroundColor: '#fff' } : { backgroundColor: '#3ba39a' },
{
width: itemSize, height: itemSize, borderRadius: containerSize / 2,
justifyContent: "center", alignItems: "center",
transform: [{ rotate: '-90deg' }]
}]} >
<Text>{props.x}</Text>
</View>
</TouchableOpacity>
)
}
const MemoizedItem = memo(Item);
and the render method
addItem(){
this.map = [];
for (let col1 = 0; col1 < itemPerCol; col1++) {
this.map[col1] = [];
for (let row1 = 0; row1 < itemPerRow; row1++) {
this.map[col1][row1] = <MemoizedItem x={row1} y={col1} />
}
}
this.setState({ map: this.map })
}
renderItem() {
return this.state.map.map((row) => {
return row.map((item) => {
return item;
})
})
}
The ideal is when you click on a circle, the color change and the other circle not render again.
I'm trying using memo like above but it still so laggy.
Is there any way to make a better performance and is there any way to check if not clicked circle is re-rendered?
Use useMemo to memo component's props and state.
Pass [ props.x, props.y, count ] to make component re-render only when some of that values change.
import { useMemo } from 'react';
const Item = (props) => {
const [count, setCount] = useState(0);
return useMemo(() => (
<TouchableOpacity>
{ console.log(`Item x${props.x}-y${props.y} render`) }
...
</TouchableOpacity>
), [ props.x, props.y, count ]);
};
Add key props to make sure that Item component will not re-mount.
<Item key={`x${row1}-y${col1}`} x={row1} y={col1} />