React Native, how to execute a component's method from App.js when using stack navigation

250 views Asked by At

I finished a React Native course, and I'm trying to make a chat app to practice.

Summarize the problem:

  • I have 2 screens ,ContactList.js and ChatRoom.js
  • I have a Navigation stack with these two screens Navigation.js
  • The Navigation component is imported and rendered in App.js
  • I added FCM module to handle notifications

The goal is to execute the function that loads messages in the chatroom _loadMessages(), when the app receives a notification on foreground state. And to execute the function (I didn't create it yet) to update unread message in a global state.

What I've tried

I followed react native firebase docs, I have a function that handle notification on foreground declared inside App.js. The problem is that I can't tell the other components (the screens) to execute their functions. The "Ref" method can't be used cause I'm not calling the child component (the screens) directly inside the App.js, I'm calling and rendering the Navigation.js Stack instead.

So, in this case, when we have a navigation component called on app.js, how can we tell other components to execute a function that is declared inside them?

App.js

import React, { useEffect } from 'react'
import Navigation from './Navigation/Navigation'
import messaging from '@react-native-firebase/messaging';

export default function App() {

  requestUserPermission = async () => {
    //On récupere le token
    const token = await messaging().getToken();
    console.log('TOKEN: ' + token)
    const authStatus = await messaging().requestPermission();
    const enabled =
      authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
      authStatus === messaging.AuthorizationStatus.PROVISIONAL;

    if (enabled) {
      console.log('Authorization status:', authStatus);
    }
  }

  handleForegroundNotification = () => {
    const unsubscribe = messaging().onMessage(async remoteMessage => {
      console.log('A new FCM message arrived!', JSON.stringify(remoteMessage));
    });
    return unsubscribe;
  }

  useEffect(() => {
    this.requestUserPermission();
    this.handleForegroundNotification();
  }, []);

  return (
    <Navigation />
  )
}

Navigation.js

import { createAppContainer } from "react-navigation"
import { createStackNavigator } from "react-navigation-stack"
import ContactList from '../Components/ContactList'
import ChatRoom from '../Components/ChatRoom'

const ChatStackNavigator = createStackNavigator({
    ContactList: {
        screen: ContactList,
        navigationOptions: {
            title: 'Contacts'
        }
    },
    ChatRoom: {
        screen: ChatRoom,
        navigationOptions: {
            title: 'Conversation'
        }

    }
})

export default createAppContainer(ChatStackNavigator)

ChatRoom.js

import React from 'react'
import { View, StyleSheet, Text, Image, SafeAreaView, TextInput, Alert, FlatList, ActivityIndicator } from 'react-native'
import { sendMessage } from '../API/sendMessageApi'
import { getMessages } from '../API/getMessagesApi'
import MessageItem from './MessageItem'

class ChatRoom extends React.Component {
    constructor(props) {
        super(props)
        this.message = ""
        this.contact = this.props.navigation.getParam('contact')

        this.state = {
            defautInputValue: "",
            listMessages: [],
            isLoading: true
        }
    }

    _textInputChanged(text) {
        this.message = text
    }
    _sendMessage() {
        this.setState({ defautInputValue: " " });
        sendMessage('1', this.contact.id_contact, this.message).then(() => {
            this._loadMessages();
        });
    }

    _loadMessages() {
        getMessages('1', this.contact.id_contact).then((data) => {
            this.setState({ listMessages: data, isLoading: false, defautInputValue: "" })
        });
    }
    componentDidMount() {
        this._loadMessages();
    }
    _displayLoading() {
        if (this.state.isLoading) {
            return (
                <View style={[styles.loading_container]}>
                    <ActivityIndicator size="large" color="orange" />
                </View>
            )
        }
    }

    render() {
        //console.log('Contact ID: ' + JSON.parse(this.contact))
        return (
            <SafeAreaView style={styles.container}>
                <View style={styles.contact}>
                    <View style={styles.image_container}>
                        <Image
                            style={styles.image}
                            source={{ uri: 'https://moonchat.imedramdani.com/avatar/' + this.contact.avatar }}
                        ></Image>
                    </View>
                    <View style={styles.username_container}>
                        <Text style={styles.username}>{this.contact.username}</Text>
                    </View>
                </View>
                {/* BODY */}
                <View style={styles.body}>
                    <FlatList
                        ref={ref => this.flatList = ref}
                        onContentSizeChange={() => this.flatList.scrollToEnd({ animated: true })}
                        data={this.state.listMessages}
                        keyExtractor={(item) => item.id.toString()}
                        renderItem={({ item }) =>
                            <MessageItem
                                message={item}
                            />}
                    >
                    </FlatList>
                </View>
                <View style={styles.input_container}>
                    <TextInput
                        style={styles.input}
                        onChangeText={(text) => this._textInputChanged(text)}
                        onSubmitEditing={() => this._sendMessage()}
                        defaultValue={this.state.defautInputValue}
                        placeholder="Aa"
                    ></TextInput>
                </View>
                {this._displayLoading()}
            </SafeAreaView>
        )
    }
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        backgroundColor: '#1d2733',
    },
    contact: {
        height: 50,
        backgroundColor: '#1d2733',
        marginTop: 0,
        flexDirection: 'row',
        borderBottomColor: 'grey',
        borderWidth: 1
    },
    username_container: {
        //backgroundColor: 'red',
        flex: 3,
        justifyContent: 'center',
        alignItems: 'flex-start'
    },
    username: {
        fontSize: 20,
        color: 'white'
    },
    image_container: {
        //backgroundColor: 'blue',
        flex: 1,
        justifyContent: 'center'
    },
    image: {
        //backgroundColor: 'yellow',
        height: 45,
        width: 45,
        marginLeft: 10,
        borderRadius: 25
    },
    body: {
        //backgroundColor: 'red',
        flex: 1
    },
    input_container: {
        height: 75,
        //backgroundColor:'blue',
        padding: 5
    },
    input: {
        paddingLeft: 20,
        height: 50,
        backgroundColor: 'white',
        borderWidth: 1,
        borderRadius: 25,
        borderColor: '#D5D5D5',
        fontSize: 20
    },
    loading_container: {
        position: 'absolute',
        left: 0,
        right: 0,
        top: 0,
        bottom: 0,
        alignItems: 'center',
        justifyContent: 'center'
    },
});

export default ChatRoom

Thanks!

1

There are 1 answers

0
ED Bowman On

I set up a solution that worked, but I don't know if it is a proper way.

I restored App.js

import React from 'react'
import Root from './Root'
import { Provider } from 'react-redux'
import Store from './Store/configureStore'

class App extends React.Component {

  render() {
    return (
      <Provider store={Store}>
        <Root />
      </Provider>
    )
  }
}

export default App

I created a now component Root.js which contains notification handler

import React from 'react'
import Navigation from './Navigation/Navigation'
import messaging from '@react-native-firebase/messaging'
import { connect } from 'react-redux'

class Root extends React.Component {

    requestUserPermission = async () => {
        //On récupere le token
        const token = await messaging().getToken();
        console.log('TOKEN: ' + token)
        const authStatus = await messaging().requestPermission();
        const enabled =
            authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
            authStatus === messaging.AuthorizationStatus.PROVISIONAL;

        if (enabled) {
            console.log('Authorization status:', authStatus);
        }
    }

    handleForegroundNotification = () => {
        const unsubscribe = messaging().onMessage(async remoteMessage => {
            console.log('A new FCM message arrived!', JSON.stringify(remoteMessage));
            const action = { type: "RELOAD_MESSAGES", value: '1' }
            this.props.dispatch(action)
        });
        return unsubscribe;
    }

    componentDidMount() {
        this.requestUserPermission();
        this.handleForegroundNotification();
    }
    render() {
        return (
            <Navigation />
        )
    }
}

const mapStateToProps = (state) => {
    return {
        loadyourself: state.loadyourself
    }
}

export default connect(mapStateToProps)(Root)

The store is provided in App.js to let Root.js access the global state.

When a notification is received at foreground, the Root.js update a key in the global state named "loadyourself". When the state is updated, the ChatRoom.js which is connected to the store too, trigger the componentDidUpdate()

componentDidUpdate() {
    if (this.props.loadyourself == "1") {
        this._reloadMessages();
    }
}

of course, to avoid the infinite loop, the _reloadMessages() restore default value in the global state of loadyourself key

_reloadMessages() {
    const action = { type: "RELOAD_MESSAGES", value: '0' }
    this.props.dispatch(action)
    getMessages('1', this.contact.id_contact).then((data) => {
        this.setState({ listMessages: data })
    });
}

The messages are updated, the global state is re-initialized, the componentDidUpdate() does not trigger until next notification.

It works. Like I said, I don't know if there is a more proper way, I'm new in React-Native (2 weeks). I am open to other solutions