React Native - How To Access Refs? - react-native

I'm trying to access this.refs so I can scroll to the top of a Flat List but it is undefined.
My render method is this:
render() {
return (
<View style={styles.container}>
{ this.state.loading &&
<ActivityIndicator />
}
{ !this.state.loading &&
<FlatList
ref='flatList'
data={this.state.facebookPosts}
onEndReachedThreshold={0}
onEndReached={({ distanceFromEnd }) => {
this._getFacebookPosts(this.state.nextPost);
}}
renderItem={({item}) => <FacebookPost
date={item.created_time}
message={item.message}
image={item.attachments.data[0].media.image.src}
url={item.link}
/>}
/>
}
</View>
)
}
}
As you can see, I have the ref set as 'flatList'.
I then have a tab bar that when you click on one of the tabs, it is meant to scroll to the top of the list. (I am trying just for not to console log out the this.refs but getting undefined)
static navigationOptions = {
tabBarLabel: 'News',
showIcon: true,
tabBarIcon: ({ tintColor }) => (
<Image
source={require('../assets/images/newspaper-icon.png')}
style={[styles.icon, {tintColor: tintColor}]}
/>
),
tabBarOnPress: (scene, jumpToIndex) => {
// this.refs.listRef.scrollToOffset({x: 0, y: 0, animated: true})
console.log(this.refs);
jumpToIndex(scene.index);
}
};
What do I need to do differently to get this.refs to not be undefined inside that onTabBarPress function?

The problems in your code are
ref takes a function not a string
scrollToOffset does not use x, y as parameters. If you want to scroll to a certain index, use scrollToIndex instead.
bind isLoading with refreshing is better than inject your FlatList after loading
The code below should work:
class MyListView extends React.Component {
_listRef;
...
render() {
return (
<View>
<FlatList
ref={ref => {this._listRef = ref; }}
data={this.state.facebookPosts}
renderItem={({item}) => <FacebookPost
date={item.created_time}
message={item.message}
image={item.attachments.data[0].media.image.src}
url={item.link}
/>}
...
refreshing={this.state.loading}
/>
}
</View>
)
}
scrollToIndex = (idx) => {
this._listRef.scrollToIndex({ index: idx, animated: true });
}
}

In my option, you only can access to refs that are in the same class.
You do what you wanna do but using componentDidMount when it mounts you scroll the list

It appears that you are trying to access a property on the class instance inside a static property which knows nothing about the instance. Static properties are tied to the class not the class instance and so their this keyword is not a reference to the class instance.
It looks like you are using react-navigation and so you could hack the instance (this) into your static navigationOptions property by doing this in componentDidMount.
componentDidMount() {
this.props.navigation.setParams({ instance: this });
}
And then you can define your navigationOptions as a function like this to get access to your navigation params.
static navigationOptions = ({ navigation }) => ({
tabBarLabel: 'News',
showIcon: true,
tabBarIcon: ({ tintColor }) => (
<Image
source={require('../assets/images/newspaper-icon.png')}
style={[styles.icon, {tintColor: tintColor}]}
/>
),
tabBarOnPress: (scene, jumpToIndex) => {
navigation.state.params.instance.refs.listRef.scrollToOffset({x: 0, y: 0, animated: true})
jumpToIndex(scene.index);
}
});
This may not be the best way, but it is a way to accomplish it.

Related

React Navigation V5 does not receive focused parameter when using custom bottomTab component

I am currently trying to implement a custom tabBar design into my react native app which is using React Navigation 5 as the navigation library. Everything is working correctly, except that my tabBarIcons don't receive any props, so i cannot determine whether i have to show the active or inactive tabIcon. Whenever i use a default tabbar i do receive the props, so there must be something wrong in my custom tabbar. I did follow the docs though, and only find the instruction to emit the 'tabPress' event. I do however think that i should emit more events to get the correct focused prop. I have set up the navigator like this:
const Tabs = createBottomTabNavigator();
export default () => (
<Tabs.Navigator tabBar={TabBarComponent} initialRouteName="Home">
<Tabs.Screen
name="Home"
component={HomeScreen}
options={{
tabBarIcon: ({ focused }) => {
// The props here are {}, so focused is undefined.
const icon = focused
? require('images/iconOverviewRed.png')
: require('images/iconOverviewGrey.png');
return <Image source={icon} />;
},
}}
/>
<Tabs.Screen
name="Overview"
component={OverviewScreen}
options={{
tabBarIcon: props => {
console.log(props);
return <Image source={require('images/logoRed.png')} />;
},
}}
/>
<Tabs.Screen
name="Account"
component={AccountScreen}
options={{
tabBarIcon: ({ focused }) => {
const icon = focused
? require('images/iconAccountRed.png')
: require('images/iconAccountGrey.png');
return <Image source={icon} resizeMethod="resize" />;
},
}}
/>
</Tabs.Navigator>
);
And this is my custom tabBar compnent:
const TabBar = ({ navigation, state, descriptors }: any) => {
return (
<View style={styles.container}>
{state.routes.map((route: any) => {
const onPress = () => {
const event = navigation.emit({
type: 'tabPress',
target: route.key,
canPreventDefault: true,
});
if (!event.defaultPrevented) {
navigation.dispatch({
...TabActions.jumpTo(route.name),
target: state.key,
});
}
};
return (
<TabIcon
key={route.key}
Icon={descriptors[route.key].options.tabBarIcon}
onPress={onPress}
isBig={route.name === 'Home'}
/>
);
})}
</View>
);
};
const TabIcon = ({ onPress, Icon, key, isBig }: any) => {
return (
<TouchableWithoutFeedback key={key} onPress={onPress}>
<View style={isBig ? styles.bigTab : styles.defaultTab} key={key}>
<Icon />
</View>
</TouchableWithoutFeedback>
);
};
Thanks in advance.
descriptors[route.key].options just gives you the options as you have specified them. If you log the value of descriptors[route.key].options.tabBarIcon, you'll see that it prints the function that you have specified.
In your custom tab bar, it's upto you to use the option as you need. Since it's a function here, you'll have to call it and pass desired arguments.
descriptors[route.key].options.tabBarIcon({ focused: state.index === index })
This also means that you fully control the option. You can put whatever type you'd like, function, a require statement directly etc. and then use that. You also don't have to call it tabBarIcon, you can call it whatever you want.

React navigation 5.0 header button

I trying to add right navigation header button directly on from Component, and implementation steps have been changed navigation 5.0 version, there is one method that provide add button with method
function HomeScreen({ navigation }) {
const [count, setCount] = React.useState(0);
navigation.setOptions({
headerRight: () => (
<Button onPress={() => setCount(c => c + 1)} title="Update count" />
),
});
return <Text>Count: {count}</Text>;
}
but need to implement on it
export default class HomeScreen extends Component {
constructor() {
super()
}
render() {
return ()
}
}
You can do this in your component constructor
this.props.navigation.setOptions({
headerRight: () => <Button />
});
Try this
<Stack.Screen
code..//
options={{
code...//
headerRight: () => (
<Button
onPress={() => alert('This is a button!')}
title="Info"
color="#fff"
/>
),
}}
/>

navigationOptions in screen component is not working

If I set the navigationOptions in the screen component inside a StackNavigator to change the header of the stack inside a DrawerNavigator the navigationOptions are not taken into account. I can however pass defaultNavigationOptions which are taken into account. If I don't pass defaultNavigationOptions it doesn't change the behavior. This is with react-navigation v 4.x
here I create the Stack navigators screens for my Drawer
drawerNavigator = () => {
let drawerRoutes = {}
this.state.routes.forEach(r => {
let stackRoute = {}
let t = () => <First name={r} />
stackRoute[r] = { screen : t }
let stackOptions = {
initialRouteName: r,
defaultNavigationOptions: stackNavigationOptions
}
let s = createStackNavigator(stackRoute, stackOptions)
drawerRoutes[r] = { screen : s }
})
let drawerOptions = {
drawerWidth: '75%',
initialRouteName: this.state.routes[0],
contentComponent: props => <Menu {...props} />,
contentOptions: drawerItemsOptions
}
return createDrawerNavigator(drawerRoutes, drawerOptions);
}
Then in my component I try to set the navigationOptions of the Stack but it is not taken into account...
export default class First extends Component {
static navigationOptions = ({ navigation }) => {
return {
headerTitle: 'hahahaha',
headerRight: () => (
<Button
onPress={() => alert('This is a button!')}
title="Info"
color="#fff"
/>
),
}
}
render() {
return (
<SafeAreaView style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
<StatusBar transluscent backgroundColor="transparent" barStyle="dark-content" />
<Text>{this.props.name}</Text>
</SafeAreaView>
);
}
}
Okay I've discovered that if I pass my screen that way
stackRoute[r] = { screen : First }
instead of the
let t = () => <First name={r} />
stackRoute[r] = { screen : t }
the navigationOptions are taken into account. However I cannot pass props that way....

Sending props back to any screen using back button override

I have only been working with React-Native recently. For this reason, I would need help with the following problem:
I did override the back button action in the header of a stack navigator, to navigate to any screen. I have to "send back" props to the screen xy. I have tried it on several variants, but unfortunately get error messages (because it does not work as I want) or do not get the expected result. I would be grateful for a solution. Thank you!
static navigationOptions = ({navigation}) => ({
title: null,
headerStyle: {backgroundColor: 'transparent'},
headerLeft: <HeaderBackButton
onPress={() => {
this.props.navigation.state.params.returnData(CurrentServiceProvider,'ServiceProvider')
navigation.push('digitalCode'),{ Info1: SomeObject, Info2: SomeObject }
}}
/>
})
Edit:
Screen B:
export class ScreenB extends React.Component {
static navigationOptions = ({navigation}) => ({
title: null,
headerStyle: {backgroundColor: 'transparent'},
headerLeft: <HeaderBackButton onPress={() => {
navigation.state.params.goBackData({SomeObject, 'ObjectIdentifier'});
navigation.push('ScreenA')}
}
/>
})
}
Screen A:
export class ScreenA extends React.Component {
getBackData = (data) => console.log(data)
ForwardNextScreen(){
this.props.navigation.navigate('ScreenB', {goBackData: this.getBackData});
}
}
If you want to send data back to previous screen ,the correct way is to declare callback on navigationParams (Github issue)
ScreenA
getBackData = (data) => console.log(data)
changeScreen = () => this.props.navigation.navigate('ScreenB', { goBackData: this.getBackData })
ScreenB
this.props.navigation.state.params.goBackData({
//your data json or whatever
})
this.props.navigation.pop()
Edit
Please change your from
headerLeft: <HeaderBackButton onPress={() => {
navigation.state.params.goBackData({SomeObject, 'ObjectIdentifier'});
navigation.push('ScreenA')}
}
/>
to
headerLeft: <HeaderBackButton onPress={() => {
navigation.getParam('goBackData')({SomeObject, 'ObjectIdentifier'});
navigation.pop();
}
/>

React Navigation - How to use this.state in Header navigationOptions?

I have spent a couple hours to find a code to handle the state in navigationOptions, but I don't get it till now,
I have a code :
static navigationOptions = ({ navigation }) => {
const { params = {} } = navigation.state
return {
headerLeft: <FontAwesome name='arrow-left' size={20} color="#FFF" onPress={() => navigation.navigate('Home')} style={{margin: DeviceWidth*0.04}}/>,
// here I want to show the TextInput if the `HeaderRight pressed` and show the `String` for the first time
headerTitle: this.state.showSearch ? <TextInput
placeholder="this is placeholder"
placeholder="search"
underlineColorAndroid='transparent'
placeholderTextColor= 'gray'
minWidth={DeviceWidth*0.75}
style={{borderWidth:1, borderColor:'grey', backgroundColor:'white', borderRadius:50}}
/> : 'My Patient',
// Here I want to set the state of `showSearch` to visible at `onPress`
headerRight: <FontAwesome name='search' size={20} color="#FFF" onPress={() => params.handleRemove()} style={{margin: DeviceWidth*0.04}}/>,
}
}
componentDidMount () {
this.props.navigation.setParams({ handleRemove: this.removeVehicle })
}
removeVehicle = () => {
this.setState({showSearch: !this.state.showSearch})
}
constructor(props){
super(props);
this.state = {showSearch: false}
}
when I run the code, I have an error
TypeError: undefined is not an object (evaluating '_this3.state.showSearch')
It is possible to show/hide the headerTitle depending on this.state.showSearch?
You can do this in the following easy way
static navigationOptions = ({ navigation }) => {
const { params = {} } = navigation.state
return {
headerTitle: params.showSearch ? 'New Title' : 'Alternate Title'
// Similarly for the rest
}
}
changeTitle = () => {
const {showSearch} = this.state
// Assuming you have access to the navigation props
this.props.navigation.setParams({
showSearch
})
this.setState({showSearch: !showSearch})
}