Camille
Camille

Reputation: 23

Callback from a header button react-navigation x5

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:

enter image description here

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

Answers (3)

Raj Talekar
Raj Talekar

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

Yannick
Yannick

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

krishnazden
krishnazden

Reputation: 1177

Try passing props like this headerRight: (props) => ( .... )

Upvotes: 0

Related Questions