React Navigation: Share state between two screens within TabNavigator - react-native

I'm learning react native by building a simple chat app. I have two screens wrapped in a TabNavigator where the first screen (Screen A) being the chatbox, and the other screen (Screen B) which displays a list of online users. I'm using SocketIO to fetch these users.
Problem is, how can I access the "onlineUsers" state from ScreenA to ScreenB so I can see an updated list of online users whenever I receive a "user joins" event?
Screen A:
export default class ScreenA extends Component {
constructor(props) {
super(props);
this.state = {
onlineUsers = [];
}
}
componentDidMount() {
// Update list of online users when new user joins chat
this.socket.on('user joins', (payload) => {
this.setState({
onlineUsers: payload.users
})
})
}
}
Screen B:
export default class ScreenB extends Component {
constructor(props) {
super(props);
// I want to get the onlineUsers from ScreenA
this.state = {
onlineUsers = [];
}
}
}
Router:
export const Chat = TabNavigator({
ChatBox: {
screen: ScreenA
},
OnlineUsers: {
screen: ScreenB
},
})
PS: I'm using react-navigation to handle navigation

Best way is to handle events in the parent component and then passing it to their children components. So in your case, you should have a online user list in your router. Then pass the array to screen B. Here is how you should do
Router
state = {
online_users:[]
}
_update = (data) => {
this.setState({online_users:data});
};
export const Chat = TabNavigator({
ChatBox: {
screen: <ScreenA onUpdate={this._update}/>
},
OnlineUsers: {
screen: <ScreenB userList={this.state.online_users}>
},
})
Screen A
export default class ScreenA extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
// Update list of online users when new user joins chat
this.socket.on('user joins', (payload) => {
this.props.onUpdate(payload.users)
})
}
}
Screen B
export default class ScreenB extends Component {
constructor(props) {
super(props);
}
// You can access online user using this.props.userList
}

I came across this post when I ran into a similar issue earlier this year so I thought I'd post my solution.
I would say your best bet in this situation is to use a Custom Navigator to wrap your TabNavigator which will expose <TabNavigator /> in your custom navigator allowing you to pass any methods or state down to ScreenA and ScreenB as screenProps.
The custom navigator would look like:
import React from 'react'
import { ChatTabNavigator } from './ChatTabNavigator'
class StateManagingCustomNavigator extends React.Component {
static router = ChatTabNavigator.router
state = {
onlineStatus: [theStatus]
}
handleMessagePosted = () => {
// Handle message post, etc...
}
// ... SocketIO code and other logic to manage the state here? ...
render() {
const { navigation } = this.props
const { onlineStatus } = this.state
return (
<ChatTabNavigator
screenProps={{
theStatus: onlineStatus,
chatMessagePosted: this.handleMessagePosted
}}
navigation={navigation}
/>
)
}
}
export default StateManagingCustomNavigator
From here you could implement an event system as #Ashish Prakash suggested, or manage all of your state in the custom navigator and transform ScreenA and ScreenB into presentational components.

Use this.props.navigation.setParam({onlineUsers: onlineUsers: payload.users}) when you get user-list from server.
Then use it in Screen B like this.props.navigation.state.params.onlineUsers

Related

How do i switch screens from one file to another

I'm very new to react native so please explain carefully.i have 3 files App.js SplashAndLogin.js and Register.js Im able to get from the initial screen to the register screen and go back and forth between my components in my Register file but when its time to go back to the Login screen i always seem to get the same error.
I've tried several different things but they all keep giving me the same error. im starting to think the way i set up my files are just wrong.
//App.js
class App extends Component<Props> {
render() {
return (
<AppContainer/>
)
}
}
export default App
const AppSwitchNavigator = createSwitchNavigator(
{
Login: {screen: SplashAndLogin},
//Registe :{screen: Register}
});
//SplashAndLogin.js
class SplashAndLogin extends Component<Props> {
render() {
return (
<AppContainer/>
)
}
}
export default SplashAndLogin;
const SAndLAppNavigator = createSwitchNavigator(
{
SandL : {screen: LoadingScreen },
RegisterScreen : {screen: Register}
}
);
//Register.js
export default class Application extends Component<Props> {
render() {
return (
<AppContainer/>
);
}
}
const AppSwitchNavigator = createStackNavigator(
{
Login :{screen: NameScreen},
PhoneAndEmail: {screen: EmailPasswordScreen},
HomeScreen: {screen: SplashAndLogin },
UploadScreen: {screen: CertificateUploadScreen }
});
const AppContainer = createAppContainer(AppSwitchNavigator);
So to summarize i can get to every screen except when im on the Register.js file and try to navigate to the HomeScreen it throws out the error
"The component for route 'HomeScreen' must be a react component For example...."
It's normal, you are using two different AppContainer.
You can not call a view in another AppContainer from the current AppContainer
this.props.navigation
refers to the current navigation
Use the state to change AppContainer
class RenderAppContainer extends Component {
constructor(props) {
super(props);
this.state = { IsConnected : false }
this.changeIsConnected = this.changeIsConnected.bind(this)
}
changeIsConnected = () => {
this.setState((prevState, props ) => ({ IsConnected : !prevState.IsConnected}))
}
render() {
cons {IsConnected } = this.state
return(
<React.Fragment>
{(IsConnected) ? AppContainerOne : AppContainerTwo } 
</React.Fragment
)
}
}
You can then pass changeIsConnected to your AppContainer
<AppContainerOne screenProps={{changeConnected: this.changeIsConnected}} />
<AppContainerTwo screenProps={{changeConnected: this.changeIsConnected}} />
https://reactnavigation.org/docs/en/stack-navigator.html
see documentation for ScreenProps

React native reload child component

I have 2 screen A and Screen B. Screen A has a sub component SomeList which loads data and i just pass the action to the subcomponent and it does the rest.Currently by design of react-navigation no action on revisit. I googled and came across https://reactnavigation.org/docs/en/navigation-events.html but not sure how to use it to reload SomeList when i navigate from ScreenB to ScreenA.
ScreenA
class ScreenA extends Component {
render() {
return (
<SomeList
loadData={this.props.actions.getAlllist}
/>
)
}
ScreenB
Class ScreenB extends Component {
someAction = () => {
this.props.navigation.navigate("ScreenA")
}
}
Due to react-native's design, I think you'd be better off passing data to your child Component.
Here's how you can do this:
class ScreenA extends Component {
constructor(props) {
super(props)
this.state = {
data: [],
}
this.props.navigation.addListener('didFocus', this.load)
this.props.navigation.addListener('willBlur', this.unmount)
}
load = ()=>{
const _data = this.props.actions.getAlllist;
this.setState({data:_data})
}
unmount = ()=>{
this.setState({data:[]})
}
render() {
return (
<SomeList
dataArray ={this.state.data}
/>
)
}
};
If you want to fetch data each time you render something, you can use componentDidMount lifecycle method
class ScreenA extends Component {
state = {
data: [];
}
componentDidMount() {
this.setState({ data: this.props.actions.getAlllist });
}
render() {
return <SomeList loadData={this.state.data}/>;
}
So every time the ScreenA screen it's rendered it will fetch the data.

Dynamic route with React Navigation

I have a react native app running with react navigation 3.9 with 2 components Signup and Event:
export default class Signup extends React.Component {
}
export default class Event extends React.Component {
}
Also there is a splash component which retrieve local token.
Whenever there is a token retrieved from local drive, then the initial route is Event. Otherwise the it is Signup.
const stack = createStackNavigator ({
Event: Event,
Signup: Signup,
},{
InitialRouteName: InitRoute //<<either Event or Signup
})
const initScreen = createSwitchNavigator({
Splash: Splash,
App: stack,
})
export default createAppContainer(initScreen)
Here InitRoute needs to be set by checking local token which is retrieved in splash component. Dynamic routes is not very straight forward with react navigation. What is a good way to implement it with react navigation?
You can create dynamic routes based on the token. You'll need a screen that renders those two routes. Like
// app renders createStackNavigator with Event and Signup
const routes = {
Event: {
screen: Event,
navigationOptions: {
title: 'Event',
},
},
Signup: {
screen: Signup,
navigationOptions: {
title: 'Signup',
},
},
};
class App extends React.Component {
// creates dynamic routes
createDynamicRoutes = initiaRoute => {
return createAppContainer(
createStackNavigator(routes, {
initialRouteName: initiaRoute,
})
);
};
render() {
// get initial route from splash screen
// this.props.navigation.navigate('App', { init: 'init screen' });
const initiaRoute = this.props.navigation.state.params.init || 'Event';
// create routes and set initial route
const AppContainer = this.createDynamicRoutes(initiaRoute);
return <AppContainer />;
}
}
InitScreen navigator renders App and Splash
const InitScreen = createSwitchNavigator({
Splash: Splash,
App: App,
})
export default createAppContainer(InitScreen);
Demo

How to Re-render a component that was already rendered after activating a props in other component

I'm setting up an application in React-native where I have a:
Component A : a search component with 2 fields
Component B : a button on this page where I click on it, the 3rd field appears
This components are only linked with react-navigation
In my case, the component B is a component where I can buy premium, and I want to update the component A when premium bought.
The problem : when I already rendered the Component A, and the I go to Component B, click the button, the Component A does not re-render, how can I do it ?
I'm looking for something like this :
class ComponentA extends PureComponent {
render() {
if (userHasNotClickedOnComponentB) {
return (
<SearchForm/>
)
} else {
return (
<SearchFormPremium/>
)
}
}
}
SearchForm and SearchFormPremium are two separated Component:
One with the Premium functionalities, the other one for normal users only
I already rendered ComponentA, and then I go to ComponentB and click the button
class ComponentB extends PureComponent {
render() {
return (
<Button onClick={() => setPremium()}/>
)
}
}
How can the ComponentA re-render so i can have the changes of ComponentB ?
Thanks
You may want to look into using Redux, or something of the like to keep a centralized store that all of your components can look at. There are plenty of Redux tutorials out there so I wont go into details, but essentially it will allow you to:
1) Create a data store accessible from any 'connected' component
2) Dispatch actions from any component to update the store
When you connect a component, the connected data becomes props. So, for example, if you connected component A and B to the same slice of your store, when component A updates it, component B will automatically re-render because its props have changed.
Redux github page
Okay, with Redux it worked !
Just connect both component. In ComponentA (the component that has to be automatically updated) use the function componentWillReceiveProps() and refresh it inside of it.
In Reducer :
const initialState = {premium: false};
const tooglePremiumReducer = (state = initialState, action) => {
switch (action.type) {
case "TOOGLE_PREMIUM":
return {
...state,
premium: action.payload.premium,
};
default:
return state;
}
};
export default tooglePremiumReducer;
In Action :
export const tooglePremiumAction = (premium) => {
return dispatch => {
dispatch({
type: "TOOGLE_PREMIUM",
payload: {
premium: premium
}
});
};
};
In ComponentB :
// Import tooglePremiumAction
class ComponentB extends PureComponent {
render() {
return (
<Button onClick={() => this.props.tooglePremiumAction(true)}/>
)
}
}
const actions = {
tooglePremiumAction
};
export default connect(
actions
)(ComponentB);
In ComponentA:
class ComponentA extends PureComponent {
componentWillReceiveProps(nextProps) {
if(this.props.premium !== nextProps.premium) {
//here refresh your component
}
}
render() {
if (!this.props.premium) {
return (
<SearchForm/>
)
} else {
return (
<SearchFormPremium/>
)
}
}
}
const mapStateToProps = state => {
const premium = state.premium.premium
return { premium };
};
export default connect(mapStateToProps)(ComponentA);

How to send parameters from screen to stack navigator?

I am a newbie to react native.
I am using a stack navigator inside a tab navigator; I have to navigate multiple screens inside each tab. i am able to send parameters from my default class HomePage to my nested classes in my tab and stack navigators.
export const MyApp = TabNavigator({
Asset: {
screen: AssetScreen,
},
Sensors: {
screen: sensorsStack,
},
Settings: {
screen: settingStack
},
}
export const sensorsStack = StackNavigator({
sensors : { screen: SensorScreen },
sensorDetails : { screen: SensorDetails }
});
export const settingStack = StackNavigator({
settings: { screen: SettingsScreen },
about : { screen: About },
environment : { screen: Environment }
});
export default class HomePage extends Component {
constructor(props) {
super(props);
this.state = {
assetID:'xyz',
authToken:'xyzz'
}
}
static navigationOptions = {
header: null
};
render() {
const screenProps = {
asset: {
assetID: this.state.assetID,
authToken : this.state.authToken,
},
}
return (
<MyApp screenProps={screenProps} />
);
}
}
Now, i want to send a parameter from 'SensorScreen' to 'SensorDetails'. I have tried sending parameters using
NavigationActions.navigate({ routeName: 'sensorDetails' ,params: { sensorType:'Fuel',}});
from 'SensorScreen' class. But was not able to get the parameter in 'SensorDetails' class. How can i pass this params?
A little late answer but might help others that ends up here.
Instead of using NavigationActions you could try navigation from sensorScreen to sensorDetails with navigate and passing some extra variables.
this.props.navigation.navigate('SensorDetails',{sensorType:'Fuel'})
The passed object can then be read in the second screen by
this.props.navigation.state.params.sensorType
A minimal example of the case can be seen in these lines. Observe that this is a stupid on icon tab bar. But it is kept that way since the question was about a tab bar with stacks on each tab. And new tabs are easily added.
import React,{Component} from 'react'
import { Text, View, Footer,Button } from 'react-native'
import {StackNavigator,TabNavigator} from 'react-navigation'
export class SensorScreen extends React.Component {
render() {
return (<Button
onPress={() => this.props.navigation.navigate('SensorDetails',{sensorType:'Fuel'})}
title="Go to Sensor Details"
/>)}}
export class SensorDetails extends React.Component {
render() {
return (<View>
<Text>{this.props.navigation.state.params.sensorType}</Text>
</View>);}
}
const sensorsStack = StackNavigator({
sensors : { screen: SensorScreen },
SensorDetails : { screen: SensorDetails }
});
const MyApp = TabNavigator({
Sensors: {
screen: sensorsStack,
},
});
export default class Nested extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<MyApp/>
);
}
}
Hope this helps.