Conflict with react-native class static and react context(High Order Component) - reactjs

(Clarification i made my app using expo init)
I was trying to mix firebase and react-native and came across to this problem
NavigateScreen.js
class NavigateScreen extends React.Component {
static navigationOptions = {
title: 'Campus Navigator',
headerStyle: {
backgroundColor: '#ffa000',
borderBottomColor: 'black',
borderBottomWidth: 0,
},
};
...
}
export default withFirebase(NavigateScreen);
context.js
export const withFirebase = Component => props => (
<FirebaseContext.Consumer>
{firebase => <Component {...props} firebase={firebase} />}
</FirebaseContext.Consumer>
);
basically it passes a firebase instance to a component. but the problem is the static navigationOptions just won't appear on the GUI.

Higher order components does not pass (unless specifically implemented) static navigationOptions to the child component.
You can fix this by using the following structure.
const withFirebaseNavigateScreen = withFirebase(NavigateScreen);
withFirebaseNavigateScreen.navigationOptions = ({ navigation }) => ({
title: 'Campus Navigator',
headerStyle: {
backgroundColor: '#ffa000',
borderBottomColor: 'black',
borderBottomWidth: 0,
},
});
export default withFirebaseNavigateScreen;

Related

StyleSheet.create() inside React Native component

I understand that I should normally use StyleSheet.create() outside of the component, but lets say that I want to set the backgroundColor based on theme received from useTheme() hook from react-navigation. Since I can't call this hook outside of the component, I have to invoke it inside the component then do something like this:
export default () => {
const { colors } = useTheme()
return <View style={[styles.container, {backgroundColor: colors.background}]}></View>
}
Now, what if I implement above like this:
export default () => {
const { colors } = useTheme()
const componentStyles = useMemo(
() =>
StyleSheet.create({
container: {
...styles.container,
background: colors.background
}
}),
[colors]
)
return <View style={componentStyles.container}></View>
}
Is this a good practice? Specially when I have lot of cases like this and it makes the code looks messy.
You can also use state for backgroundColor. See:
export default () => {
const { colors } = useTheme();
const [background, setBackground] = React.useState();
React.useEffect(() => {
setBackground(colors.background);
}, [colors])
return
<View style={[styles.container, {backgroundColor: background}]} />
}
It's worth adding that StyleSheet.create() is not mandatory for creating styles in react native. An interesting discussion on this topic can be found here: Is there a need to use StyleSheet.create? . Personally, I do not use StyleSheet.create() .
Using TypeScript in react native I just create an object with the correct types. Example:
type Styles = {
modalView: ViewStyle;
button: ViewStyle;
modalText: TextStyle;
};
const styles: Styles = {
modalView: {
margin: 20,
backgroundColor: 'white',
borderRadius: 20,
padding: 35,
alignItems: 'center',
shadowRadius: 4,
elevation: 5,
},
button: {
padding: 10,
elevation: 2,
},
modalText: {
marginBottom: 15,
textAlign: 'center',
},
};
Hooks can only be used inside the components. If you need to add more styling from your theme into your components, as you know style prop takes an array. You can do something like this:
style={
[styles.container, //that comes from your StyleSheet created outside of the component
{
backgroundColor: theme.colors.your_color_choice, //Your theme values that comes from your useTheme hook
borderRadius: theme.borderRadius.your_radius_value,
},
]}
So that way you keep your styles object outside of the component, and they are not newly-created on each re-render.

Using Redux state in defaultNavigationOptions

I am using React-navigation and redux in my app.
I have to change header color according to state. I have saved color inside state and my colors came from Api call.
I have used navigationOptions with redux according to docs.
static navigationOptions = ({ navigation }) => ({
title: "MyApp",
headerStyle: {
backgroundColor: navigation.getParam('primary_color'),
},
headerTintColor: navigation.getParam('primary_color_text')
})
componentWillMount() {
this.props.navigation.setParams({
...this.props.appSettings
});
}
But I'm getting white header for 1 sec then my color one, due to props setting by params. So is there any way I can connect defaultNavigationOptions?
You can change defaultNavigationOptions in the same way you do navigationOptions, any parameters you set can be accessed there as well.
defaultNavigationOptions = ({ navigationData }) => {
let params = navigationData.navigation.state.params || {...yourDefaultParams};
return {
title: "MyApp",
headerStyle: {
backgroundColor: params["primary_color"],
},
headerTintColor: params["primary_color_text"],
};
});
It will, however give the same delay on changing the colors. You will have to attack the problem from a different angle. Have a look a Edward Starlight's suggested solution:
react navigation v3 set screenProps through AppContainer
You can achieve that by creating a custom navigator like this:
class NavWrapper extends Component {
static router = AppNavigator.router;
render() {
const { navigation } = this.props;
return <AppNavigator
navigation={navigation}
screenProps={{
settings: this.props.settings,
headerTintColor: '#ffffff'
}}
/>;
}
}
const AppNavWrapper = connect(state => ({settings: state.settings}))(NavWrapper);
const AppContainer = createAppContainer(AppNavWrapper);
And then in your root component
render() {
return (
<AppContainer/>
);
}

React-navigation to re-render route when parent state changes

I have a page which receives data from a source in specific Intervals and sets them into a state, I show part of that data inside my page, and I have a button which opens another page(via react-navigation) and I want rest of data to be displayed in there, but the problem is, it won't refresh the second page when state of parent changes, below is simplified code:
import React from "react";
import { View, Text, Button } from "react-native";
import { createStackNavigator, createAppContainer, NavigationActions } from "react-navigation";
class HomeScreen extends React.Component {
constructor() {
super();
this.state = { number: 0 };
}
genNumber() {
number = Math.random();
this.setState({ number });
}
componentWillMount() {
setInterval(() => { this.genNumber() }, 1000);
}
render() {
return (
<View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
<Text>Home Screen</Text>
<Button title="Go" onPress={() => this.props.navigation.navigate('Test', { k: () => this.state.number })} />
</View>
);
}
}
class TestScreen extends React.Component {
constructor() {
super()
this.state = { t: 1 };
}
render() {
return (
<View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
<Text>Test {this.props.navigation.getParam('k')()}</Text>
</View>
);
}
}
const AppNavigator = createStackNavigator({
Home: {
screen: HomeScreen,
},
Test: {
screen: TestScreen
}
});
export default createAppContainer(AppNavigator);
you are using navigation params to pass data from parent class to child class and you want a reflection of state change in child class but it won't work in it.
For that use Component and pass data as props you immediate reflection of data.
React will not re-render a screen if a data passed to it through parameters changed, unfortunately this is how react navigation works.
To solve it, you can change the way your are passing data between screens.
instead of using navigation parameters use a global state(example redux).
in this case set the number variable to a state in redux,
then select that number in your Test screen and when number changes it will trigger a re-render.

USING DrawerNavigator AND BottomTabNavigator HOW TO PUT DrawerIcon

Im using my main menu with DrawerNavigator.
Inside a screen i use BottomTabNavigator.
I put the DrawerIcon of each screen like this:
....
export default class HomeScreen extends React.Component {
...
static navigationOptions = {
drawerIcon: ({ tintColor }) => (
<Icon name="home" style={{ fontSize: 24, color: tintColor }} />
)
}
...
}
But, in the screen that i use the TabNavigator i dont export a class, y export createBottomNavigator, like so:
class MetasSemanaAnterior extends React.Component {...}
class MetasScreen extends React.Component {...}
export default createBottomTabNavigator({
MetasMain: MetasScreen,
MetasAnterior: MetasSemanaAnterior,
});
I dont know where put my navigationOptions of the DrawerNavigator!
Help Please! TY
Not Sure if this is what you are looking for but you could try this if i am understanding it correctly.
You could refer https://reactnavigation.org/docs/en/navigation-options-resolution.html for things to make sense.
const tabNavigator = createBottomTabNavigator({
MetasMain: MetasScreen,
MetasAnterior: MetasSemanaAnterior,
});
tabNavigator.navigationOptions = () => {
}
export default tabNavigator;
i solved in this way:
in the main screen, where i create my Drawer Navigation, i set the DrawerIcon. So that, the icon appear despite i dont set it in the child Screens
set DrawerIcon where you create the DrawerMenu:
const AppDrawerNavigator = createDrawerNavigator({
ChatBot: {
screen: HomeScreen
},
Profile: {
screen: ProfileScreen
},
METAS: {
screen: MetasScreen,
navigationOptions: {
//here i set the drawerIcon
drawerIcon: ({ tintColor }) => { return (<Icon name="flag" style={{ fontSize: 24 }} />) }
}
}
}

How to set NavigationOptions of a parent?

I have nested navigator in my app. I would like my header bar to be set on the root of the navigaton, so there will be a mutual header bar to all screens in my application.
my index.js is:
const StackOpportunityNavigator = createStackNavigator(
{
MainOpportunity: OpportunityScreen,
ClientScreen: ClientScreen,
BusinessMapLocation: BusinessMapLocation,
LoginScreen: LoginScreen
},
{
initialRouteName: 'MainOpportunity',
headerMode: 'none',
transitionConfig: () => fromLeft(600),
}
);
const DrawerNavigationOptions = createDrawerNavigator(
{
OpportunityStack: { screen: StackOpportunityNavigator },
History: HistoryScreen,
MyChances: MyChancesScreen,
Info: InfoScreen
},
{
contentComponent: (props) => <SideBar {...props} />,
drawerPosition: 'right',
transitionConfig: () => fromLeft(600),
}
);
const LoginNavigator = createStackNavigator(
{
LoadingScreen: LoadingScreen,
LoginScreen: LoginScreen,
DrawerNavigator: DrawerNavigationOptions
},
{
transitionConfig: () => fromLeft(600),
headerMode: 'screen',
navigationOptions: {
headerStyle: {
backgroundColor: Colors.primary
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'normal',
fontSize: 18
}
}
}
);
export default LoginNavigator;
In every screen in my application i have this code:
static navigationOptions = {
header: <HeaderBar title={'currentScreenTitle'} />
};
However it works from 'LoginScreen' screen and all the others from 'LoginNavigator', but not from any other screen like 'History'.
How can i still control the navigation options from other screen like 'History' and set the title, if my navigation header bar is in the most root navigator?
Hope you understand my question.
Really hope to for your answers as fast as possible.
Thanks guys!
From what I understand there is no way to access the parent navigator from within a nested screen.
The documentation states: "You can only modify navigation options for a navigator from one of its screen components. This applies equally to navigators that are nested as screens."
It's not very convenient but I think creating your own component is the only way to achieve this. There is a helpful library that will make creating your header component easier though. React Native Elements
I have been having the same issue what I did is made header null in navigationOptions and added my header component in screen as a regular component and performed all the logic, Its not the best practice but a work around :)

Resources