Reputation: 23
I'm trying to call a function withing a header button in react-navigation V5 to implement a sharing feature.
But I am stuck since it's no longer possible to use the static navigationOptions function.
Feature screenshot:
Component filmDetail.js
import React from 'react'
import { StyleSheet, View, Text, ActivityIndicator, ScrollView, Image, TouchableOpacity, Share, Platform } from 'react-native'
import { getFilmDetailFromApi, getImageFromApi } from '../API/TMDBApi'
import moment from 'moment'
import numeral from 'numeral'
import { connect } from 'react-redux'
import EnlargeShrink from '../Animations/EnlargeShrink'
class FilmDetail extends React.Component {
constructor(props) {
// il faut binder la fonction shareFilm dans le constructor afin de définir son state avant de la lancer depuis la abrre de navigation
super(props)
this.state = {
film: undefined,
isLoading: false
}
this._shareFilm = this._shareFilm.bind(this)
}
// Fonction pour faire passer la fonction _shareFilm et le film aux paramètres de la navigation. Ainsi on aura accès à ces données au moment de définir le headerRight
_updateNavigationParams() {
this.props.navigation.setParams({
shareFilm: this._shareFilm,
film: this.state.film
})
}
// Dès que le film est chargé, on met à jour les paramètres de la navigation (avec la fonction _updateNavigationParams) pour afficher le bouton de partage
componentDidMount() {
const favoriteFilmIndex = this.props.favoriteFilms.findIndex(item => item.id === this.props.route.params.idFilm)
if (favoriteFilmIndex !== -1) {
this.setState({
film: this.props.favoriteFilms[favoriteFilmIndex]
}, () => { this._updateNavigationParams() })
return
}
this.setState({ isLoading: true })
getFilmDetailFromApi(this.props.route.params.idFilm).then(data => {
this.setState({
film: data,
isLoading: false
}, () => { this._updateNavigationParams() })
})
}
_displayLoading() {
if (this.state.isLoading) {
return (
<View style={styles.loading_container}>
<ActivityIndicator size='large' />
</View>
)
}
}
_toggleFavorite() {
const action = { type: "TOGGLE_FAVORITE", value: this.state.film }
this.props.dispatch(action)
}
_displayFavoriteImage() {
var sourceImage = require('../Images/ic_favorite_border.png')
var shouldEnlarge = false // Par défaut, si le film n'est pas en favoris, on veut qu'au clic sur le bouton, celui-ci s'agrandisse => shouldEnlarge à true
if (this.props.favoriteFilms.findIndex(item => item.id === this.state.film.id) !== -1) {
sourceImage = require('../Images/ic_favorite.png')
shouldEnlarge = true // Si le film est dans les favoris, on veut qu'au clic sur le bouton, celui-ci se rétrécisse => shouldEnlarge à false
}
return (
<EnlargeShrink
shouldEnlarge={shouldEnlarge}>
<Image
style={styles.favorite_image}
source={sourceImage}
/>
</EnlargeShrink>
)
}
_displayFilm() {
const { film } = this.state
if (film != undefined) {
return (
<ScrollView style={styles.scrollview_container}>
<Image
style={styles.image}
source={{uri: getImageFromApi(film.backdrop_path)}}
/>
<Text style={styles.title_text}>{film.title}</Text>
<TouchableOpacity
style={styles.favorite_container}
onPress={() => this._toggleFavorite()}>
{this._displayFavoriteImage()}
</TouchableOpacity>
<Text style={styles.description_text}>{film.overview}</Text>
<Text style={styles.default_text}>Released on {moment(new Date(film.release_date)).format('DD/MM/YYYY')}</Text>
<Text style={styles.default_text}>Note : {film.vote_average} / 10</Text>
<Text style={styles.default_text}>Score : {film.vote_count}</Text>
<Text style={styles.default_text}>Budget : {numeral(film.budget).format('0,0[.]00 $')}</Text>
<Text style={styles.default_text}>Genre(s) : {film.genres.map(function(genre){
return genre.name;
}).join(" / ")}
</Text>
<Text style={styles.default_text}>Studio(s) : {film.production_companies.map(function(company){
return company.name;
}).join(" / ")}
</Text>
</ScrollView>
)
}
}
_shareFilm() {
const {film} = this.state
Share.share(
{
title: film.title,
message: film.overview
}
)
}
_displayFloatingActionButton() {
const {film} = this.state
if (film != undefined && Platform.OS === 'android') {//Uniquement sur Android lorsque le film est chargé
return (
<TouchableOpacity
style={styles.share_touchable_floatingactionbutton}
onPress={() => this._shareFilm()}>
<Image
style={styles.share_image}
source={require('../Images/ic_share.png')}/>
</TouchableOpacity>
)
}
}
render() {
return (
<View style={styles.main_container}>
{this._displayLoading()}
{this._displayFilm()}
{this._displayFloatingActionButton()}
</View>
)
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
MainStackNavigator.js
// Navigation/Navigation.js
import * as React from 'react'
import { NavigationContainer } from '@react-navigation/native'
import { StyleSheet, Image, TouchableOpacity} from "react-native";
import { createStackNavigator } from '@react-navigation/stack'
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'
import Search from '../Components/Search'
import FilmDetail from '../Components/FilmDetail'
import Favorites from '../Components/Favorites'
import { Ionicons } from '@expo/vector-icons'
const Stack = createStackNavigator()
function Main({navigation: {setParams} }) {
return(
<Stack.Navigator>
<Stack.Screen
name='Search'
component={Search}
options={{
title: 'Search'
}}
/>
<Stack.Screen
name="FilmDetail"
component={FilmDetail}
options={({ navigation }) => ({
title: 'Details',
headerRight: () => (
<TouchableOpacity
style={styles.share_touchable_headerrightbutton}
onPress= {() => navigation.alert('Nope')} >
<Image
style={styles.share_image}
source = {require('../Images/ic_share_ios.png')} />
</TouchableOpacity>
)
})
}
/>
<Stack.Screen
name="Favorites"
component={Favorites}
options={{
title: 'Favoris'
}}
/>
</Stack.Navigator>
)
}
const Tab = createBottomTabNavigator()
export default function HomeTabs() {
return (
<NavigationContainer>
<Tab.Navigator
screenOptions={({ route }) => ({
tabBarIcon: ({ focused, color, size }) => {
let iconName
if (route.name === 'Search') {
iconName = focused
? 'ios-search'
: 'ios-search'
} else if (route.name === 'Favorites') {
iconName = focused
? 'ios-heart'
: 'ios-heart';
}
return <Ionicons name={iconName} size={size} color={color} />
},
})}
tabBarOptions={{
activeTintColor: 'tomato',
inactiveTintColor: 'gray',
}}
>
<Tab.Screen name="Search" component={Main} />
<Tab.Screen name="Favorites" component={Favorites} />
</Tab.Navigator>
</NavigationContainer>
)
}
const styles = StyleSheet.create({
share_touchable_headerrightbutton: {
marginRight: 8
},
share_image: {
width: 30,
height: 30
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Thanks for your help!
Upvotes: 1
Views: 1231
Reputation: 39
@Yannick 's Answer can be good solution although it is not recommended to set state of a component from another component. In rear cases, you can wrap your setState in useEffects. Reference: React Documentation
useEffect(() => {
this.props.navigation.setOptions({
headerRight: () => (
<TouchableOpacity
onPress={() => this.share()}
>
<Ionicons name="md-share" size={24} color={Colors.text} />
</TouchableOpacity>
)
});
});
Upvotes: 2
Reputation: 26
As mentioned in the react-native-navigation to pass a callback from your component to your header you must use setOption like below :
this.props.navigation.setOptions({
headerRight: () => (
<TouchableOpacity
onPress={() => this.share()}
>
<Ionicons name="md-share" size={24} color={Colors.text} />
</TouchableOpacity>
)
});
Doc link : https://reactnavigation.org/docs/troubleshooting/
Hope that helps :-)
Upvotes: 1