where I should initiate and connect socket.io in React Native component? - react-native

i now learning socket.io to build a chat app with React Native that contain private messaging like line/whatsapp, user can add another user to their friendlist, and can start chat with them...
Here's my Stack Navigation looks like:
class MainNavigator extends Component {
render() {
const { user } = this.props;
return (
<Stack.Navigator>
{user.username ? (
<>
<Stack.Screen name="ChatList" component={ChatList} options={{ title: 'Chatz' }} />
<Stack.Screen
name="Chat"
component={Chat}
options={{ title: `Chat as "${user.username}"` }}
/>
</>
) : (
<>
<Stack.Screen name="Login" component={Login} options={{ headerShown: false }} />
<Stack.Screen name="Register" component={Register} options={{ headerShown: false }} />
</>
)}
</Stack.Navigator >
);
}
}
You can see that if there is a user logged in in my app, stack navigator only contain ChatList and Chat, where the ChatList is a list of all active Chats.
I know that to connect a socket to backend we simply connect it with socket = io(http://localhost:3000), but where i should connect it in my components?
I'll use the socket in ChatList and Chat component.
Should i connect it in both of the component constructor like this?
class ChatList extends Component {
constuctor(props) {
super()
this.socket = io('localhost:3000');
}
.......
}
class Chat extends Component {
constuctor(props) {
super()
this.socket = io('localhost:3000');
}
.......
}
Or somewhere else just once? Because if i did it like above, it connecting several times to my express backend...

You should only connect to socket server once and switch connection when needed. My suggestion is create a class service that control connection logic and all component that need to listen to this connection channel will subcribe to the service. Example:
class SocketService {
constructor() {
this.connection = null
this.listener = []
}
connect(server) {
this.connection = io(server)
this.connection.<on_receive_messages> = this.onMessageReceived(data)
}
addListener(identify, callback = () => {}) {
this.listener[identify] = callback
}
removeListener(identify) {
delete this.listener[identify]
}
onMessageReceived(data) {
this.listener.map(callback => callback(data))
} ​
}
Then once user logged in, let connect your service
onUserLoggedIn() {
socket = new SocketService();
socket.connect("localhost:3000");
navigation.navigate("ChatList");
}
And inside your component
class ChatList extends Component {
componentDidMount() {
socket.addListener("ChatList", (message) => {
Alert.alert("message", message)
})
}
componentWillUnmount() {
socket.removeListener("ChatList");
}
.......
}
class Chat extends Component {
componentDidMount() {
socket.<switch_channel_or_something>
socket.addListener("Chat", (message) => {
Alert.alert("message", message)
})
}
componentWillUnmount() {
socket.removeListener("Chat");
}
}
It's not a complete code but you might get the idea

Related

React Navigation linking initialRouteName not working when opening app from quit state

I have followed the documentation of React Navigation and implemented an authentication flow as well the Linking mechanism for notifications via the linking prop of NavigationContainer.
When I open a notification when the app is running, e.g. the link https://domain/transactions/history, I am redirected to the screen TransactionHistory, and when I go back I am redirected to ChargingStations (as I want)
But, when I do the same thing from a quit state, I am being redirected as well but I can't go back to ChargingStations and I see the warning (The action GO_BACK was not handled by any navigator)..
My code is a follow:
App.tsx
export default class App extends React.Component<Props, State> {
public state: State;
public props: Props;
public centralServerProvider: CentralServerProvider;
public deepLinkingManager: DeepLinkingManager;
private appVersion: CheckVersionResponse;
private readonly navigationRef: React.RefObject<NavigationContainerRef<ReactNavigation.RootParamList>>;
private readonly appContext;
private initialUrl: string;
public constructor(props: Props) {
super(props);
this.navigationRef = React.createRef();
this.appContext = {
handleSignIn: () => this.setState({isSignedIn: true}),
handleSignOut: () => this.setState({isSignedIn: false})
};
this.state = {
switchTheme: false,
navigationState: null,
showAppUpdateDialog: false,
isSignedIn: undefined
};
}
public setState = (
state: State | ((prevState: Readonly<State>, props: Readonly<Props>) => State | Pick<State, never>) | Pick<State, never>,
callback?: () => void
) => {
super.setState(state, callback);
};
public async componentDidMount() {
// Get the central server
this.centralServerProvider = await ProviderFactory.getProvider();
// Setup notifications
await Notifications.initialize();
// Check for app updates
this.appVersion = await Utils.checkForUpdate();
// Set
this.setState({
showAppUpdateDialog: !!this.appVersion?.needsUpdate,
isSignedIn: true
});
}
public render() {
const { switchTheme, showAppUpdateDialog, isSignedIn } = this.state;
return switchTheme ? (
<NativeBaseProvider>
<GestureHandlerRootView style={{ flex: 1 }}>
{showAppUpdateDialog && (
<AppUpdateDialog appVersion={this.appVersion} close={() => this.setState({ showAppUpdateDialog: false })} />
)}
{isSignedIn == null ?
<Loading />
:
this.createRootNavigator()
}
</GestureHandlerRootView>
</NativeBaseProvider>
) : (
<NativeBaseProvider>
<View />
</NativeBaseProvider>
);
}
private buildLinking(): LinkingOptions<ReactNavigation.RootParamList> {
return (
{
prefixes: DeepLinkingManager.getAuthorizedURLs(),
getInitialURL: () => this.initialUrl,
subscribe: (listener) => {
// Listen for background notifications when the app is running,
const removeBackgroundNotificationListener = messaging().onNotificationOpenedApp(async (remoteMessage: Notification) => {
const canHandleNotification = await Notifications.canHandleNotificationOpenedApp(remoteMessage);
if (canHandleNotification) {
this.setState({isSignedIn: true}, () => listener(remoteMessage.data.deepLink));
}
});
return () => {
removeBackgroundNotificationListener();
};
},
config: {
screens: {
AuthNavigator: {
screens: {
Login: 'login'
}
},
AppDrawerNavigator: {
initialRouteName: 'ChargingStationsNavigator', // <-- Initial screen I would like to always be present as first screen when navigating
screens: {
ChargingStationsNavigator: {
initialRouteName: 'ChargingStations',
screens: {
ChargingStations: 'charging-stations/all'
}
},
InvoicesNavigator: 'invoices',
TransactionInProgressNavigator: {
screens: {
TransactionsInProgress: 'transactions/inprogress'
}
},
TransactionHistoryNavigator: {
screens: {
TransactionsHistory: 'transactions/history'
}
}
}
}
}
}
}
);
}
private createRootNavigator() {
const { isSignedIn } = this.state;
return (
<AuthContext.Provider value={this.appContext}>
<SafeAreaProvider>
<NavigationContainer
onReady={() => this.onReady()}
linking={this.buildLinking()}
ref={this.navigationRef}
onStateChange={(newState) => this.setState({navigationState: newState})}
initialState={this.state.navigationState}
>
<rootStack.Navigator initialRouteName="AuthNavigator" screenOptions={{ headerShown: false }}>
{isSignedIn ?
<rootStack.Screen name="AppDrawerNavigator" children={createAppDrawerNavigator} />
:
<rootStack.Screen options={{animationTypeForReplace: 'pop'}} name="AuthNavigator" children={createAuthNavigator} />
}
</rootStack.Navigator>
</NavigationContainer>
</SafeAreaProvider>
</AuthContext.Provider>
);
}
}
Expected behavior
I expect the ChargingStations screen to always be present as first screen, even from a quit state
Reproduction
https://github.com/sap-labs-france/ev-mobile/tree/upgrade_react_native
Platform
[X] Android
[X] iOS
Environment
[x] I've removed the packages that I don't use
package
version
#react-navigation/native
6.0.14
#react-navigation/drawer
6.5.1
#react-navigation/material-bottom-tabs
6.2.5
#react-navigation/stack
6.3.5
react-native-safe-area-context
4.4.1
react-native-screens
3.18.2
react-native-gesture-handler
2.8.0
react-native-reanimated
2.13.0
react-native
0.70.6
node
16.13.0
npm or yarn
9.1.2
You should define a nested linking route: Docs
const config = {
screens: {
Home: {
screens: {
Profile: 'users/:id',
},
},
},
};
TransactionHistoryNavigator: {
screens: {
ChargingStationsNavigator: {
screens: {
TransactionsHistory: 'transactions/history'
}
}
}
}
Maybe you can try to redeclare initial route (unfortunately I have currently no access to verify my suggestion):
TransactionHistoryNavigator: {
screens: {
TransactionsHistory: {
initialRouteName: 'ChargingStationsNavigator', //Define initial route here
}
}
}
I have managed to obtain the desired behavior by using following code in the drawer navigator props.
backBehavior={'initialRoute'}

React Native Linking Deep Link Doesn't Open on First Load

I'm trying to enable deeplink in my React Native project, and have it working for all use-cases where the deeplink opens the app from the background, not from the first load. I believe the issue is that my path that I am deep-linking, is not available until after the "loading screen" has gone away.
For example I have this setup as my RootStack.Navigator
<RootStack.Navigator
detachInactiveScreens={false}
headerMode="none"
initialRouteName={"AppTabsScreen"}
screenOptions={{ animationEnabled: false }}
mode="modal"
>
{isLoading ? (
<RootStack.Screen
name="LoadingScreen"
component={LoadingScreen}
options={{ animationEnabled: true }}
/>
) : user ? (
<RootStack.Screen name="AppTabsScreen" component={AppTabsScreen} />
) : (
<RootStack.Screen name="AuthStackScreen" component={AuthStackScreen} />
)}
...
And my linking config as:
const deepLinksConf = {
screens: {
AppTabsScreen: {
screens: {
initialRouteName: "Activity",
Activity: {
screens: {
Activity: "activity",
Details: "workout/:userId/:id",
},
},
Goals: {
screens: {
Goals: "goal",
GoalDetail: "goal/:id",
},
},
Settings: "settings",
Profile: "profile",
},
},
}
};
In my getInitialURL() function, I get the URL correctly, but I believe the path is already on the LoadingScreen, and not the AppTabsScreen, which causes it not to fire. Is there anyway around this situation? How can I delay the deeplink to wait until the AppTabScreen is present to process the link? This works fine when the app is the fore or background, but never at first start.
....
async getInitialURL() {
console.log("fired");
// Check if app was opened from a deep link
const url = await Linking.getInitialURL();
console.log("deeplinking");
console.log(url);
if (url != null) {
console.log("just returning");
return url;
}
// Check if there is an initial firebase notification
const message = await messaging().getInitialNotification();
// Get deep link from data
// if this is undefined, the app will open the default/home page
return messsage.data?.link;
},
I am using react-navigation, and my main navigation controller is as follows:
<NavigationContainer
linking={linking}
theme={scheme === "dark" ? DarkTheme : DefaultTheme}
ref={navigationRef}
onReady={() => {
if (navigationRef.current) {
routeNameRef.current = navigationRef?.current.getCurrentRoute().name;
}
}}
onStateChange={async () => {
const previousRouteName = routeNameRef.current;
const currentRouteName = navigationRef?.current.getCurrentRoute().name;
if (previousRouteName !== currentRouteName) {
await analytics().logScreenView({
screen_name: currentRouteName,
screen_class: currentRouteName,
});
}
// Save the current route name for later comparision
routeNameRef.current = currentRouteName;
}}
>
<FlashMessage position="top" />
<RootStackScreen />
</NavigationContainer>

React navigation 5 access navigation with class components

I am currently migrating to react-navigation 5 (from react-navigation 4). My problem is that I am using class components and cannot access this.props.navigation, how do I pass the navigation to my class components and access it in my components.
My App.tsx looks like this:
export default function App(props:any) {
const Stack = createStackNavigator();
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="LoginScreen" component={LoginScreen} />
<Stack.Screen name="CreateUserScreen" component={CreateUserScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
And my pages looks like this:
interface ILoginScreenProps {
navigation:any
}
interface ILoginScreenState {
email: string
password: string
rememberlogin: boolean
//navigation: any
}
class LoginScreen extends React.Component<ILoginScreenProps,ILoginScreenState> {
gotoCreateUser = () => {
const { navigation } = this.props;
navigation.navigate('Createuser', {});
}
render() {
return ( rendering stuff.. );
}
}
I know it can be done functional, but I would currently like to keep it as class components.
I get the error:
TypeError: undefined is not an object (evaluating
'navigation.navigate')

How to pass current state of App to Tab Navigation Screen

If I'm using React Navigation v5, what is the best way to pass the current state of a parent component (in my case, the main App) down through a Tab and Stack navigator to a screen that I'd like to use the current state in?
Following the documentation, I have created a stack navigator for each tab that holds the respective screens.
App.js contains a state that needs to be used for a few things. Most importantly, it will provide badge count on the Tab navigator, as well as be a source of Flatlist data on one of the tab screens.
What is the correct approach to getting the state from App all the way down to a child component in a stack navigator in a tab navigator?
App.js
const Tab = createBottomTabNavigator()
export default class App extends React.Component {
constructor(props){
super(props)
this.state = {
neededArray: []
}
}
const updateTheArray = (newArray) => {
this.setState({
neededArray: newArray
})
}
componentDidMount(){
//Listener that searches for nearby bluetooth beacons and updates the array with the passed function
startObserver(updateTheArray)
}
componentWillUnmount(){
stopObserver()
}
render(){
return(
<NavigationContainer>
<Tab.Navigator>
<Tab.Screen
name = "Home"
component = { HomeStack }/>
<Tab.Screen
name = "About"
component = { AboutStack }/>
//The Stack that contains the screen that I need to use the App's state in
<Tab.Screen
name = "Nearby"
component = { NearbyStack }/>
</Tab.Navigator>
</NavigationContainer>
)
}
}
NearbyStack.js
//This stack holds the screen that I need to use the App's state in
const NearbyStackNav = createStackNav()
const NearbyStack = () => {
return(
<NearbyStackNav.Navigator>
<NearbyStackNav.Screen
name = "Nearby"
component = { NearbyScreen }
/>
</NearbyStackNav.Navigator>
)
}
NearbyScreen.js
//The screen that I want to use the App's state in
const NearbyScreen = () => {
return(
<View>
<FlatList
//Where I would like to use the App's state
/>
</View>
)
}
You can pass some initial params to a screen. If you didn't specify any params when navigating to this screen, the initial params will be used. They are also shallow merged with any params that you pass. Initial params can be specified with an initialParams prop:
Usage
<Tab.Screen
name = "Nearby"
component = { NearbyStack }
initialParams={{ arrayItem: this.state.neededArray }}
/>
NearbyScreen.js
React.useEffect(() => {
if (route.params?.arrayItem) {
// Post updated, do something with `route.params.arrayItem`
// For example, send the arrayItem to the server
}
}, [route.params?.arrayItem]);
My solution was to use React's Context API.
BeaconContext.js - New
import React from 'react'
const BeaconContext = React.createContext()
export default BeaconContext
App.js - Modified
import BeaconContext from './path/to/BeaconContext'
const Tab = createBottomTabNavigator()
export default class App extends React.Component {
constructor(props){
super(props)
this.state = {
neededArray: []
}
}
const updateTheArray = (newArray) => {
this.setState({
neededArray: newArray
})
}
componentDidMount(){
startObserver(updateTheArray)
}
componentWillUnmount(){
stopObserver()
}
render(){
return(
// Wrap the nav container in the newly created context!!!
<BeaconContext.Provider value = { this.state.neededArray }
<NavigationContainer>
<Tab.Navigator>
<Tab.Screen
name = "Home"
component = { HomeStack }/>
<Tab.Screen
name = "About"
component = { AboutStack }/>
<Tab.Screen
name = "Nearby"
component = { NearbyStack }/>
</Tab.Navigator>
</NavigationContainer>
</BeaconContext.Provider>
)
}
}
NearbyStack.js - Unchanged
const NearbyStackNav = createStackNav()
const NearbyStack = () => {
return(
<NearbyStackNav.Navigator>
<NearbyStackNav.Screen
name = "Nearby"
component = { NearbyScreen }
/>
</NearbyStackNav.Navigator>
)
}
NearbyScreen.js - Modified
import BeaconContext from './path/to/BeaconContext'
const NearbyScreen = () => {
return(
<View>
//Wrap the component in the new context's consumer!!!
<BeaconContext.Consumer>
{
context => <Text>{ context }</Text>
}
</BeaconContext.Consumer>
</View>
)
}
I've been struggling with the exact same issue - when using the initialProps property to pass a state to a Tab.Screen the screen never receives any updates. It reads the intial state value once then nothing.
To make it work I skipped using the initialProps property and instead used the children property on Tab.Screen like so:
App containing <Tab.Navigator> and <Tab.Screen>:
const[myBool, setMyBool] = useState(false)
<Tab.Screen
name="MyTab"
children={() => (
<MySecondScreen passedStateParam={ myBool } />
)}
.
.
.
</Tab.Screen>
MySecondScreen consuming updates on passed myBool state:
export function MySecondScreen ({ passedStateParam }) {
const myPassedBoolState = passedStateParam
React.useEffect(() => {
if(myPassedBoolState) {
//Act upon App.tsx updating the state
}
}, [myPassedBoolState])
}
Not sure if I'm missing something when trying to perform this with the initialParams property but this way (using children property) I got it to work at least.

Implement FB login with react native and redux

I want to use Redux framework in my react native based app for implementing Facebook login (I am learning Redux at the moment). I am looking for suggestions on how to structure my Facebook login code to use the redux. More specifically, what actions, reducer and store should I create?
Below is the current Facebook based login code that I have in my app (it does not use redux structure). I have deleted the unrelated code to keep things simple:
index.ios.js
class ProjectXApp extends React.Component {
constructor(props) {
// Set the use to NULL
this.state = {
user: null,
};
}
handleLogin(user) {
this.setState({
// Update the user state once the login is complete
user,
});
}
renderScene(route, navigator) {
const Component = route.component;
return (
<View style={styles.app}>
<Component
user={this.state.user}
navigator={navigator}
route={route}
/>
</View>
);
}
render() {
return (
<Navigator
renderScene={this.renderScene.bind(this)}
initialRoute={{
// Render the Login page in the beginning
component: Login,
props: {
onLogin: this.handleLogin.bind(this),
},
}}
/>
);
}
}
Login.js
// Import Facebook Login Util Component
class Login extends React.Component {
constructor(props) {
super(props);
this.state = {
// 'false' means responseToken is not required. 'true' means responseToken is required
responseToken: false,
};
}
// This method gets the fb access token, if the token is returned then
// I render the Main App component (switchToMain method). If the
// access token is not returned then I render a login Button (Refer to render method)
async getAccessToken() {
let _this = this;
await (FBSDKAccessToken.getCurrentAccessToken((token) => {
if(!token) {
_this.setState({responseToken: true})
return;
}
_this.setState({responseToken: true});
_this.props.route.props.onLogin({user: true});
_this.switchToMain();
}));
}
switchToMain() {
this.props.navigator.push({
component: Main, // Render the app
props: {
onLogOut: this.onLogOut.bind(this)
}
});
}
componentDidMount() {
this.getAccessToken();
}
onLoginButtonPress() {
// Shows transition between login and Main Screen
this.setState({responseToken: false})
FBSDKLoginManager.logInWithReadPermissions(['public_profile','email','user_friends'], (error, result) => {
if (error) {
alert('Error logging in');
} else {
if (result.isCancelled) {
alert('Login cancelled');
} else {
this.setState({result});
this.getAccessToken();
}
}
});
}
onLogOut() {
this.setState({responseToken: true});
}
render() {
// This component renders when I am calling getAccessToken method
if(!this.state.responseToken) {
return (
<Text></Text>
);
}
// This renders when access token is not available after calling getAccessToken
return (
<View style={styles.container}>
<TouchableHighlight
onPress={this.onLoginButtonPress.bind(this)}
>
<View>
// Login Button
</View>
</TouchableHighlight>
</View>
);
}
}
// Removed the styling code
Logout.js
import { FBSDKLoginManager } from 'react-native-fbsdklogin';
class Logout extends React.Component {
onLogOut() {
FBSDKLoginManager.logOut();
this.props.onLogOut();
this.props.navigator.popToTop();
}
render() {
return (
<View>
<TouchableHighlight
onPress={this.onLogOut.bind(this)}
>
<View
// Styles to create Logout button
</View>
</TouchableHighlight>
</View>
);
}
});
// Removed the styling code
Have you looked at this lib:
https://github.com/lynndylanhurley/redux-auth?