react-native-navigation theming with styled-components - react-native

I'm working with styled-components on my react-native project. We are using react-native-navigation to perform navigation within the application. So the question is how can I implement theme pattern from styled-components in such kind of application?
The problem is that to perform the idea of theming in terms of styled-components I have to wrap my top level component in <ThemeProvider /> like this:
<ThemeProvider theme={theme}>
<App />
</ThemeProvider>
But with react-native-navigation I don't have top level component. It has the idea of screens, so the application entry will look like this:
registerScreens(); // this is where you register all of your app's screens
// start the app
Navigation.startSingleScreenApp({
screen: { ... },
drawer: { ... },
passProps: { ... },
...
});

The answer was pretty simple. As react-native-navigation's registerComponent has possibility to pass redux store and Provider as a props:
Navigation.registerComponent('UNIQUE_ID', () => YourComponent, store, Provider);
We can create our custom Provider with both redux and styled-components Providers and pass this custom provider to the registerComponent like this:
import { Provider } from 'react-redux';
import { ThemeProvider } from 'styled-components';
import theme from './theme';
const Provider = ({ store, children }) => (
<Provider store={store}>
<ThemeProvider theme={theme}>
{children}
</ThemeProvider>
</Provider>
);
export default Provider;
For more details look #1920.

I think you can do something like this
function wrap(Component) {
return function () {
return (
<ThemeProvider theme={theme}>
<Component />
<AnotherComponent/>
</ThemeProvider>
);
};
}
function registerScreens(store, Provider) {
Navigation.registerComponent('app.SomeScreen', () => wrap(SomeScreen), store, Provider);
// more screens...
}

Related

Making navigation available to all components in React Native app

I'm using React Navigation 5 in my React Native app and I'm not exactly clear about how to make navigation available to all components. I also want to mention that my app uses Redux for state management but I didn't integrate React Navigation with Redux -- maybe I should!
My App.js renders my Navigator.js component which looks like below. BTW, my app requires authentication and one of the key functions of the navigator function is to redirect unauthenticated users to login screen.
class Navigator extends Component {
render() {
return (
<NavigationContainer>
{
this.props.isAuthenticated
? <MainMenuDrawer.Navigator drawerContent={(navigation) => <DrawerContent member={this.props.member} navigation={navigation} drawerActions={DrawerActions} handleClickLogOut={this.handleClickLogOut} />}>
<MainMenuDrawer.Screen name="Home" component={HomeStack} />
<MainMenuDrawer.Screen name="ToDoList" component={ToDoListStack} />
</MainMenuDrawer.Navigator>
: <SignInStack />
}
</NavigationContainer>
);
}
}
Then in my HomeStack, I have my Dashboard component which happens to be a class component that needs to access navigation. Here's my HomeStack:
const HomeStackNav = new createStackNavigator();
const HomeStack = () => {
return(
<HomeStackNav.Navigator screenOptions={{ headerShown: false }}>
<HomeStackNav.Screen name="DashboardScreen" component={Dashboard} />
<HomeStackNav.Screen name="SomeOtherScreen" component={SomeOtherComponent} />
</HomeStackNav.Navigator>
);
}
export default HomeStack;
Say, my Dashboard component looks like below. How do I make navigation available to this component?
class Dashboard extends Component {
handleNav() {
// Need to use navigation here...
}
render() {
return (
<Text>Welcome to Dashboard</Text>
<Button onPress={() => this.handleNav()}>Go somewhere</Button>
);
}
}
function mapStateToProps(state) {
return {
member: state.app.member
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(appActions, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Dashboard);
Do I need to pass navigation manually to each component? That seems too repetitive and prone to error. How do I make navigation available throughout my app?
Solution 1:
you can use useNavigation hook for functional component
import {useNavigation} from '#react-navigation/native';
const navigation = useNavigation();
navigation.navigate("interests");
//or
navigation.push("interests");
Solution 2:
you can use HOC withNavigation to navigation in props in any component for class component Ref
you can install #react-navigation/compat by
yarn add #react-navigation/compat
You can import like below
import { withNavigation } from '#react-navigation/compat';
you can use withNavigation like below
export default withNavigation(Dashboard)
Note: then you can use this.props.navigation in Dashboard component

How to get access to reducer inside App using React Navigation v5 in React Native?

I'm trying to build app with React Navigation v5 and stuck with Authentication flow.
Here is some code to understand what I'm trying to do:
const Stack = createStackNavigator();
const Drawer = createDrawerNavigator();
const AuthContext = React.createContext();
export default class App extends Component {
// constructor() ...
render() {
const store = configureStore(); // rootReducer
return (
<AuthContext.Provider store={store}>
<NavigationContainer>
// here I have to access my userReducer to check is user logged in and using LoginStack or Drawer
)
// my stacks
}
So in React Navigation docs Authentication flows uses function components and React Hooks inside them. But I'm using class-component, and I have my reducer in standalone file.
I tried to use connect() as I always do on child components in my app, but this won't work.
So is there any way to access (or map) reducer to App at the topmost level? Or maybe I'm doing something wrong and there is a better way to build switch between 2 separate stacks based on authentication?
Im not sure if this is the best way but you'll just have to use react hooks and redux subscribe:
export default function Navigation({store}) {
const [authorized, setAuthorized] = useState(
store.getState().user.auth !== null,
);
useEffect(() => {
const unsubscribe = store.subscribe(() => {
setAuthorized(store.getState().user.auth !== null);
});
return () => { unsubscribe(); }
});
return (
<NavigationContainer>
{authorized ?
<Stack.Navigator>...</Stack.Navigator> : <Stack.Navigator>...</Stack.Navigator>}
</NavigationContainer>
)
}
Then pass your store:
export default class App extends React.Component {
render() {
return (
<Provider store={store}>
<Navigation store={store} />
</Provider>
);
}
}

How to use MobX with react-native-navigation by Wix?

Quote from site:
We fully support Redux, MobX and other state management libraries.
But if I try to pass store and Provider to registerComponent() I still receive error, that react-mobx can't inject store that doesn't exists. Also I've tried Provider by megahertz, but it seems like this code is outdated.
Are there any ways to use react native navigation v2 with mobx?
If you create a HOC in where you provide the store with a provider it works.
const addStore = (Component, ...props) => {
return class App extends React.Component {
render() {
return (
<Provider venues={Stores}>
<Component {...{
...this.props,
...props,
}} />
</Provider>
);
}
}
};
export async function RegisterScreens() {
Navigation.registerComponent('venuesOverview', () => addStore(VenuesOverview));
}

In a React-Native app, how to use SwitchNavigator (react-navigator) and react-redux?

I'm using react-navigation's SwitchNavigator to manage my navigation based on authentication state. When Authenticated, I want to use Redux to store data I'm fetching.
My SwitchNavigator looks like this
SwitchRouteConfig = {
AuthLoading: AuthLoadingScreen,
Authenticated: AppStackNavigator,
NotAuthenticated: AuthStackNavigator,
}
SwitchConfig = {
initialRouteName: 'AuthLoading',
}
export default createSwitchNavigator(SwitchRouteConfig, SwitchConfig);
My Authenticated navigation looks like this:
// App Bottom Tab Navigator
const AppTabRouteConfig = {
AddItem: { screen: AddItem },
Items: { screen: Items },
Map: { screen: Map },
Help: { screen: Help },
}
const AppTabConfig = { initialRouteName: 'Items',}
const AppTabNavigator = new createBottomTabNavigator(
AppTabRouteConfig,
AppTabConfig)
And in my Screen we have:
class Items extends React.Component {
constructor(props){
super(props);
this.state = {};
}
componentDidMount(){
this.props.getData(); //call our redux action
}
render() {
if(this.props.isLoading){
return(
<View>
<ActivityIndicator />
</View>
)
} else {
return (
<Provider store={store}>
<SafeAreaView>
<FlatList
data={this.props.dataSource.features}
renderItem={({ item }) =>
<TouchableWithoutFeedback>
<View style={styles.listContainer}>
<Text>{item.prop1}</Text>
<Text>{item.prop2}</Text>
</View>
</TouchableWithoutFeedback>
}
/>
</SafeAreaView>
</Provider>
)
}
}
}
function mapStateToProps(state, props) {
return {
isLoading: state.dataReducer.isLoading,
dataSource: state.dataReducer.dataSource
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(Actions, dispatch);
}
export default connect(mapStateToProps,
mapDispatchToProps)(Items)
When I'm not authenticated, that works fine. I can login. When I am authenticated, I get the following error:
Invariant Violation: Could not find "store"
in either the context or props of
Connect(Items)". Either wrap the root
component in a <Provider>, or explicitly pass
"store" as a prop to "Connect(Items)".
In the reading I've done today, all the samples have a single top-level component which they wrap with . So, I'm not understanding how you instantiate the store and manage Redux without that model.
I should mention two additional things:
The initial authenticated app screen worked fine before I started to implement Redux.
I'm not trying to manage the state with Redux, just application data.
Project started with Create-React-Native-App.
Thank you!
You have to wrap the root component (the switch navigator) inside Provider to make the store available to all container components.
const SwitchRouteConfig = {
AuthLoading: AuthLoadingScreen,
Authenticated: AppStackNavigator,
NotAuthenticated: AuthStackNavigator,
}
const SwitchConfig = {
initialRouteName: 'AuthLoading',
}
const Navigator = createSwitchNavigator(SwitchRouteConfig, SwitchConfig);
// You can use a stateless component here if you want
export default class Root extends React.PureComponent {
render() {
return (
<Provider store={store}>
<Navigator />
</Provider >
);
}
}

Using Redux connect() with Layout component being passed children and receiving props

I'm having some difficulty getting my Layout component to connect because I'm passing children. Basically the way I have my application setup is that app.js houses the provider, persistgate, layout, and react-navigation navigation stack.
The only component which I haven't connected so far is my Layout, and before I started to implement some navigation on the top bar, I didn't really need it. Now I want to be able to pass the routeName to redux so that the Layout knows which route the user is on and can display appropriate buttons.
I've tried a few things, but nothing seems to work, if I manage to get the Layout to load with connecting, it doesn't seem to obtain the routes from redux store even though I've confirmed that it is there on React-Native Debugger.
app.js
export class App extends Component {
render() {
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<Layout>
<AppWithNavigationState/>
</Layout>
</PersistGate>
</Provider>
)
}
}
Layout.js
This is what I used BEFORE.
class Layout extends Component {
render() {
const currentRoute = this.props.route;
console.log(this.props.route); ////// TESTING TO SEE IF ROUTES SHOWS. IT'S ALWAYS "UNDEFINED" DESPITE BEING IN THE STORE.
headerButton = () => {
if (currentRoute==='Main') {
return (<Icon onPress={() => navigate({routeName: "PostForm"})} style={styles.headericon} name="back"></Icon>);
} else {
return (<Icon style={styles.headericon} name="menu"></Icon>)
}
}
.......
export default ({children}) => (
<Layout>
{children}
</Layout>
)
Layout.js Test (updated but still not receiving store data)
Layout.propTypes = {
children: PropTypes.node,
route: PropTypes.string,
updateRoute: PropTypes.func
};
const mapStateToProps = state => ({
route: state.route.route
});
const mapDispatchToProps = (dispatch) => {
return {
updateRoute: route => dispatch(postActions.updateRoute(route))
}
}
const LayoutTest = ({children}) => (
<Layout>
{children}
</Layout>
)
export default connect(mapStateToProps, mapDispatchToProps)(LayoutTest);
Turns out I do not even need to pass 'children' to the components. I rendered it by changing
export default ({children}) => (
<Layout>
{children}
</Layout>
)
to
export default connect(mapStateToProps, mapDispatchToProps)(Layout)
After doing some testing and realizing that the state was actually able to be read inside of the mapStateToProps function but it was not mapping to Layout. The reason I was so confused was that I really thought I needed to pass children to it the way I had it before. Luckily I never!