I have a ScrollView with a list of items. When I click on one item I navigate to a new scene (slide in from the right). However, during the transition period the two scenes overlap; they are fine after the animation/transition is done.
Here's an example:
and heres the related code:
render() {
return (
<Navigator
initialRoute={routes.frontpage()}
renderScene={(route, navigator) => {
if(route.index === 1) {
return (
<TouchableHighlight
onPress={() => navigator.pop()}>
<View style={{flex: 1}}>
<Text style={styles.header}> other </Text>
<Text> {route.post.title} </Text>
</View>
</TouchableHighlight>
)
}
return (
<View style={{flex: 1}}>
<Text style={styles.header}> Frontpage </Text>
<ScrollView>
{
this.props.posts.map(post => (
<TouchableHighlight
key={post.created}
onPress={() => {
if (route.index === 0) {
return navigator.push(routes.post(post))
}
navigator.pop();
}}>
<View>
<PostSummary
title={post.title}
upvotes={post.ups}
author={post.author}
subreddit={post.subreddit}
created={post.created_utc}
/>
</View>
</TouchableHighlight>
))
}
</ScrollView>
</View>
)
}}
/>
)
}
Any ideas on how to solve this?
Navigator uses animations during transitions between two scenes. In your case it uses fade during the transition. How about using a different animation. Navigator has Scene Transitions feature that you may try to change the animation.
Related
A react native 0.70 component displays items in FlatList. The array items is a state and is assigned value in hook useEffect. I would like to jest (0.29) it to see if a item's name is shown up on screen so a mock of state items is needed. Here is the render item code for FlatList:
return (
<TouchableOpacity testID={'listselltrade.detail'} onPress={() => {item.sell ? navigation.navigate("Deal Detail", {item:item}) : navigation.navigate("Trade Detail", item={item})} } >. <<== here is the testID
<View style={{flex:1, flexDirection:"row", alignItems:"center", width:wp("100%"), height:wp("17%")}}>
<View style={{width:wp("17%"), padding:wp("0.1%")}}>
{sourceUri && sourceAltUri ? <CachedImage source={{uri:sourceUri}} sourceAlt={{uri:sourceAltUri}} style={styles.itemImage} /> : null}
</View>
<View style={{width:wp("25%")}}>
<Text style={{fontSize:wp("4.2%"), flexWrap:"wrap"}}>{item.artwork.item_name}</Text>
</View>
<View style={{width:wp("8%")}}>
<Text style={{fontSize:wp("4.2%"), flexWrap:"wrap"}}>{helper.returnStatus(item.status)}</Text>
</View>
<View style={{width:wp("7%")}}>
<Text style={{fontSize:wp("4.2%"), flexWrap:"wrap"}}>{item.price+item.shipping_cost}</Text>
</View>
<View style={{width:wp("3%"),flexDirection:"row"}}>
{item.sell ? <Icon name='logo-usd' size={_size} /> : <Icon name='sync' size={_size} />}
</View>
<View style={{width:wp("8%")}}>
<Text style={{fontSize:wp("4.2%"), flexWrap:"wrap"}}>
{item.artwork.category.category_name}
</Text>
</View>
{item.sell && _me.id !== data.uploader_id && <View style={{width:wp("15%")}}>
<TouchableOpacity onPress={() => {navigation.push('ListBuyExg', {uploader_id:item.artwork.uploader_id, title:_sellerposter_name})} }>
<Text style={{fontSize:wp("4.2%"), flexWrap:"wrap"}}>
{_sellerposter_name}
</Text>
</TouchableOpacity>
</View> }
{!item.sell && _me.id !== data.uploader_id && <View style={{width:wp("15%")}}>
<TouchableOpacity onPress={() => {navigation.push('ListBuyExg', {uploader_id:item.artwork.uploader_id, title:_sellerposter_name})} } >
<Text style={{fontSize:wp("4.2%"), flexWrap:"wrap"}}>
{_sellerposter_name}
</Text>
</TouchableOpacity>
</View> }
<View style={{width:wp("20%")}}>
<Text style={{fontSize:wp("3%"), flexWrap:"wrap"}}>{item.active_date.substring(0,10)}</Text>
</View>
</View>
</TouchableOpacity>
);
Here is jest code:
test ('has screen detail', async () => {
const route = {params: {title:"my title", uploader_id:8}}; //8 is a test user. all is listed
const navigation = {navigate:jest.fn()};
const propsVal = {device_id:"a device id"};
const authVal = {result:"fdkfjdsl;fjdsafkl", myself:{id:1,name:"me"}};
const stateVal = {name:"me", alias:"akkus", aka:"aka"};
const items = [{artwork:{item_name:"work name"}}]; //<<==mock state items
const component = (
<NavigationContainer>
<propsContext.Provider value={propsVal}>
<authContext.Provider value={authVal}>
<stateContext.Provider value={stateVal}>
<ListSellTrade route={route} navigation={navigation}/>
</stateContext.Provider>
</authContext.Provider>
</propsContext.Provider>
</NavigationContainer>);
const wrapper = render(component);
expect(screen.getByText('my title')).toBeTruthy(); //<<==pass
expect(screen.getByText('work name')).toBeTruthy(); //<<==failed
expect(screen.getByTestId('listselltrade.detail')).toBeTruthy(); //<<==failed
})
But there is no item was rendered on screen and 2 expects were failed. What is the right way to mock state items so it can be fed into the jest for further test?
You should start by using screen.debug() to output the host element tree your component is rendering. This should help you understand what actually gets rendered in your RNTL tests.
You might also want to consult official RNTL + React Navigation example how to properly test screen components when using React Navigation.
I have a stack navigator where one of the screen uses a custom header:
import { createStackNavigator } from "#react-navigation/stack";
import * as React from "react";
import { Button, View } from "react-native";
const Stack = createStackNavigator();
function ScreenA({ navigation }) {
return (
<View style={{ flex: 1, justifyContent: "center"}}>
<Button title="Click me" onPress={() => navigation.navigate("ScreenB")} />
</View>
);
}
function ScreenB({ navigation }) {
return (
<View style={{ flex: 1 , justifyContent: "center"}}>
<Button title="Click me" onPress={() => navigation.navigate("ScreenA")} />
</View>
);
}
function TestComp() {
return (
<Stack.Navigator>
<Stack.Screen
name="ScreenA"
component={ScreenA}
options={{ header: () => <View style={{ height: 160, backgroundColor: "red" }}></View> }}
/>
<Stack.Screen name="ScreenB" component={ScreenB} />
</Stack.Navigator>
);
}
export default TestComp;
As a result, the header of ScreenA (a red bar) is visible from ScreenB. This doesn't happen on Android where the header is properly shown ONLY on ScreenA.
How can I stop the header from ScreenA from showing on ScreenB?
Solved it by using <Stack.Navigator headerMode="screen"> !
I have a list of items, each having a body and a source. Currently it renders like this:
const ListItem = (props) => {
const {body, source} = props.context;
return (
<View style={styles.item}>>
<View style={{backgroundColor: 'lightblue'}}>
<Text style={styles.body}>{body}</Text>
</View>
<View style={{backgroundColor: 'lightyellow'}}>
<Text style={styles.source}>{source}</Text>
</View>
</View>
);
}
That's a lot of nesting and containers. Could it be done more optimally?
I am going to post my comment as an answer.
Previous comment:
I guess it depends on your design, AFAIK this is fine within React Native assuming you're using an optimized way of rendering your list (e.g. using a FlatList or similar)
As per your following comment, I don't think it's monstrous at all.
Here's an alternative. However, for readability I would much prefer the snippet you posted in your question.
const ListItem = props => {
const items = [
{ key: 'body', backgroundColor: 'lightblue' },
{ key: 'source', backgroundColor: 'lightyellow' }
];
return (
<View style={styles.item}>
{
items.map(({ key, backgroundColor }) =>
<View style={{ backgroundColor }}>
<Text style={styles[key]}>
{ props[key] }
</Text>
</View>
)
}
</View>
)
}
The Text component does take backgroundColor so you can let go of the two Views:
<View style={styles.item}>
<Text style={[styles.body, {backgroundColor: '...'}]}>{body}</Text>
<Text style={[styles.source, {backgroundColor: '...'}]}>{source}</Text>
</View>
Additionally, I don't know what the styles.item consists of, but if you want to go all the way, you might replace the other container View with React.Fragment.
I've got a simple ScrollView:
<ScrollView
style={$style.category_container}
horizontal={true}
showsHorizontalScrollIndicator={false}
automaticallyAdjustContentInsets={true}
>
<Item title={'1'} />
<Item title={'2'} />
</ScrollView>
Item is a component that loads several thumbnails. My application is planned for both LTR and RTL users, so there is a change in directions for RTL.
The problem is when I'm using the RTL interface - the ScrollView is still moving from left to right, and I can't see all my thumbnails.
How can I solve it?
If someone will run into this in the future:
There isn't any 'built-in' property that will set ScrollView's direction to RTL at the moment.
However That's what worked for me:
set flexDirection: 'row-reverse' to ScrollView's style, which will order your items from right to left.
use onContentSizeChange to init list's scroll on the right side.
Here's an example:
scrollListToStart(contentWidth, contentHeight) {
if (I18nManager.isRTL) {
this.scrollView.scrollTo({x: contentWidth});
}
}
render() {
let containerStyle = I18nManager.isRTL ? styles.RTLContainer : styles.LTRContainer;
return (
<ScrollView
ref={ref => this.scrollView = ref}
onContentSizeChange={this.scrollListToStart.bind(this)}
horizontal={true}
style={[styles.buttonsContainer, containerStyle]}>
{this.renderButtons()}
</ScrollView>
)
}
const styles = StyleSheet.create({
RTLContainer: {
flexDirection: 'row-reverse'
},
LTRContainer: {
flexDirection: 'row'
}
})
you can use this way
i did this and worked for me
This solution has 2 rounds
1-first make this style for your scrollView : style={{scaleX:-1}}
2-second make this style for each of your childs in scrollView : style={{scaleX:-1}}
For Example
<ScrollView
horizontal={true}
contentContainerStyle={{height: 65}}
style={{scaleX:-1}}
showsHorizontalScrollIndicator={false}>
{
data.sports.map((data,index) => {
return(
<View key={index}
style={{width:150,height:55,backgroundColor:'yellow', marginHorizontal:4,scaleX:-1}}/>
)
})
}
</ScrollView>
As you can see my scrollview has scaleX = -1 style
Also all of my childs in scrollView has scaleX = -1 style
as scaleX is deprecated in views you can use transform:[{rotateY:'180deg'}] instead
Just in case someone has a similar problem to mine. I was doing a horizontal ScrollView with user images and needed the images to appear from right to left. Thank you Mr-Ash & Sergey Serduk for getting me there.
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
style={{
alignSelf: "center",
borderRadius: 100,
transform: [{ scaleX: -1 }],
}}>
{users.map((user, i) => {
return (
<View key={i} style={[{ transform: [{ scaleX: -1 }], zIndex: 100 - i }]}>
<UserImage leftMargin={-27} />
</View>
);
})}
<View style={{ marginRight: 27 }} />
</ScrollView>
In my case a proper solution is wrapping a <ScrollView/> component with <View/> see below:
<View style={{direction: 'rtl'}}><ScrollView ... /></View>
Direction can be set as rtl, ltr, inherit, initial, revert or unset
The best possible solution for RTL ScrollView is the combination of scrolling it to the end and flex-direction: 'row-reverse'
const scrollRef = useRef<any>();
const scrollToEnd = () => {
scrollRef.current.scrollToEnd({ animated: false });
};
<ScrollView
contentContainerStyle={{
flexDirection: "row-reverse"
}}
onContentSizeChange={scrollToEnd}
ref={scrollRefOne}
horizontal
showsHorizontalScrollIndicator={false}
>
...
</ScrollView>
we are currently running React-Native 0.33
We are trying to use the refs to go from 1 text input to another when they hit the next button. Classic username to password.
Anyone have any idea? Below is the code we are using. From other posts I've found on stack this is what they've done; however, it's telling us that this.refs is undefined.
UPDATE
So I've narrowed the problem down to
render() {
return (
<Navigator
renderScene={this.renderScene.bind(this)}
navigator={this.props.navigator}
navigationBar={
<Navigator.NavigationBar style={{backgroundColor: 'transparent'}}
routeMapper={NavigationBarRouteMapper} />
} />
);
}
If I just render the code below in the renderScene function inside of the original render it works, however with the navigator it won't work. Does anyone know why? Or how to have the navigator show as well as render the code in renderScene to appear in the original render?
class LoginIOS extends Component{
constructor(props) {
super(props);
this.state={
username: '',
password: '',
myKey: '',
};
}
render() {
return (
<Navigator
renderScene={this.renderScene.bind(this)}
navigator={this.props.navigator}
navigationBar={
<Navigator.NavigationBar style={{backgroundColor: 'transparent'}}
routeMapper={NavigationBarRouteMapper} />
} />
);
}
renderScene() {
return (
<View style={styles.credentialContainer}>
<View style={styles.inputContainer}>
<Icon style={styles.inputPassword} name="person" size={28} color="#FFCD00" />
<View style={{flexDirection: 'row', flex: 1, marginLeft: 2, marginRight: 2, borderBottomColor: '#e0e0e0', borderBottomWidth: 2}}>
<TextInput
ref = "FirstInput"
style={styles.input}
placeholder="Username"
autoCorrect={false}
autoCapitalize="none"
returnKeyType="next"
placeholderTextColor="#e0e0e0"
onChangeText={(text) => this.setState({username: text})}
value={this.state.username}
onSubmitEditing={(event) => {
this.refs.SecondInput.focus();
}}
>
</TextInput>
</View>
</View>
<View style={styles.inputContainer}>
<Icon style={styles.inputPassword} name="lock" size={28} color="#FFCD00" />
<View style={{flexDirection: 'row', flex: 1, marginLeft: 2, marginRight: 2, borderBottomColor: '#e0e0e0', borderBottomWidth: 2}}>
<TextInput
ref = "SecondInput"
style={styles.input}
placeholder="Password"
autoCorrect={false}
secureTextEntry={true}
placeholderTextColor="#e0e0e0"
onChangeText={(text) => this.setState({password: text})}
value={this.state.password}
returnKeyType="done"
onSubmitEditing={(event)=> {
this.login();
}}
focus={this.state.focusPassword}
>
</TextInput>
</View>
</View>
</View>
);
}
Try setting the reference using a function. Like this:
<TextInput ref={(ref) => { this.FirstInput = ref; }} />
Then you can access to the reference with this.FirstInput instead of this.refs.FirstInput
For a functional component using the useRef hook. You can use achieve this easily, with...
import React, { useRef } from 'react';
import { TextInput, } from 'react-native';
function MyTextInput(){
const textInputRef = useRef<TextInput>(null);;
return (
<TextInput ref={textInputRef} />
)
}
Try changing the Navigator's renderScene callback to the following (based on Navigator documentation) cause you will need the navigator object later.
renderScene={(route, navigator) => this.renderScene(route, navigator)}
Then, use 'navigator' instead of 'this' to get the refs.
navigator.refs.SecondInput.focus()