React Navigation: Utilizing deep linking with Redux - react-native

In React Navigation, deep linking can be utilized by passing uriPrefix prop to the top level navigator, or by passing {containerOptions: {URIPrefix: PREFIX}} as a second parameter to a built-in navigator (e.g. StackNavigator).
When Redux is integrated with React Navigation, the top level navigator is passed a navigation prop.
But, when enabling both Redux and and deep linking on a RN app. uriPrefix and navigation prop both need to be passed to a top level navigator, which throws an error,
This navigator has both navigation and container props, so it is
unclear if it should own its own state.
const S1 = () => <View><Text>S1 text</Text></View>;
const S2 = () => <View><Text>S2 text</Text></View>;
const S3 = () => <View><Text>S3 text</Text></View>;
const AppNav = StackNavigator(
{
S1: {screen: S1, path: 's1'},
S2: {screen: S2, path: 's2'},
S3: {screen: S3, path: 's3'}
}
);
#connect(state => ({nav: state.nav}))
class App extends Component {
render() {
const
{ dispatch, nav } = this.props,
uriPrefix = Platform.OS == 'android' ? 'http://localhost/' : 'http://';
return (
<AppNav
navigation={addNavigationHelpers({dispatch: this.props.dispatch, state: this.props.nav})}
uriPrefix={uriPrefix}
/>
);
}
}
const navReducer = (state, action) => (AppNav.router.getStateForAction(action, state) || state);
const rootReducer = combineReducers({nav: navReducer});
const RootApp = props =>
<Provider store={createStore(rootReducer)}>
<App />
</Provider>;
export default RootApp;
How can Redux and deep linking (using React Navigation) both be integrated in a RN app?

See #kristfal comment on
Redux can not be used with deep linking #1189.
Or #grabbou comment.

I would leverage the redux store. Consider wanting to deep link to book called "Some Title"
Pass basic query string params to your app launcher eg "?route=Store/books/some-title"
When the app loads, update your state with the information from the query string. If you want to get fancy you can do most of this with the react router.
As long as your redux actions / reducers are setup correctly your app should load the store page with the book "Some Title" (again, this is a theoretical example)

Related

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>
);
}
}

Show a tab bar item or not based on Redux state in React Native

I've got a React Native app with React Navigation and I need to show or hide a tab based on Redux state. I'm using createBottomTabNavigator and have written this:
function createTabNavigator(props:TabNavigatorProps){
debugger;
const isClient = WHAT_TO_PUT_HERE??
const tabs = isClient
? {
//something
}
: {
//something else
}
return createBottomTabNavigator(
tabs,
{
defaultNavigationOptions: ({ navigation }) => ({
tabBarIcon: ... ,
tabBarLabel: ...,
}
tabBarComponent: (props) => customTabBar(props, isClient),
tabBarOptions: {
...
},
});
};
My createTabNavigator works the way as intended if I manually set isClient to true or false, though I need this from redux state.
I've also tried connecting it, and mapStateToProps get called with the correct state, however it doesn't call createTabNavigator again so state changes aren't updated to my view hierarchy.
How can I add/remove a tab (and actually re-render altogether as that tab also involves some custom styling/rendering of the whole bar) based on Redux state?
react-navigation does not provide any API to hide a tab from BottomTabNavigator.
However, it allows providing custom TabBarComponent. It also exposes BottomTabBar component which is the default TabBarComponent for BottomTabNavigator.
This BottomTabBar component takes a property getButtonComponent to get the component for rendering each tab. It is provided the tab details in argument and is expected to return component to be used for rendering that tab.
To achieve hiding of tabs dynamically, you provide your own function for getButtonComponent. Check the details of tab with your state to decide whether it should be visible or hidden. Return an 'empty component' if you want to hide it, else simply call the default.
/**** Custom TabBarComponent ****/
const BlackHole = (props) => []
let _TabBarComponent = (props) => {
const getButtonComponent = (arg) => {
const hide = props.hiddenTabs[arg.route.key];
if (hide) {
return BlackHole;
}
return props.getButtonComponent(arg)
}
return (<BottomTabBar {...props} getButtonComponent={getButtonComponent}/>);
};
const mapStateToProps = (state) => ({
hiddenTabs: state.hiddenTabs
});
const TabBarComponent = connect(mapStateToProps)(_TabBarComponent);
/**** Navigation ****/
const TabNavigator = createBottomTabNavigator({
Tab1: createStackNavigator({Screen1}),
Tab2: createStackNavigator({Screen2}),
Tab3: createStackNavigator({Screen3}),
}, {
tabBarComponent: props => (
<TabBarComponent {...props} style={{borderTopColor: '#605F60'}}/>
)
});
See this snack for POC: https://snack.expo.io/BJ80uiJUH
We had the same requirement but in react navigation we cannot have dynamic tab, as we have to define all the routes statically well in advance.
Check this answer, in which I have explained in detail about how we achieved it.
Also have mentioned other possible approach (which we have not done).
There is one more interesting article you might want to check on this.
Make sure you are not calling redux state inside a HOC Component ,
For state management ,you can try to render it inside a component , try this logic :
class TabIndex extends Component {
render() {
// coming from redux
let isClient= this.props.isClient;
return (<View>
{isClient?<TabIndexNavigator ifYouNeed={ isClient } />
:
null}
</View>)
}
}
module.exports = TabIndex
then import it normally in your main stackNavigator
const StackMain = StackNavigator(
{
TabIndex: {
screen: TabIndex
},
...
}

AWS Amplify Auth / react-navigation: How to access auth state?

In my React Native mobile application, I would like to authenticate my users using AWS Amplify Authentication and manage navigation between screens using React Navigation. I tried to implement everything "by the book", i.e. as per the docs and my App conceptually looks like this:
// import statements skipped
class FirstTabScreen extends React.Component {
render = () => {
{/* access some data passed in screenProps */}
const someData = this.props.screenProps.someData;
{/* TODO: how to access AWS Amplify auth data here ?!? */}
{/* this.props contains screenProps and navigation objects */}
console.log("this.props: ", this.props);
{/* this.props.authState is undefined */}
console.log("this.props.authState: ", this.props.authState);
return <View><Text>First Tab - some data: {someData}</Text></View>
};
}
class SecondTabScreen extends React.Component {
render = () => {
const otherData = this.props.screenProps.otherData;
return <View><Text>Second Tab - other data: {otherData}</Text></View>
};
}
const AppNavigator = createBottomTabNavigator({
FirstTab: { screen: FirstTabScreen },
SecondTab: { screen: SecondTabScreen },
});
const AppContainer = createAppContainer(AppNavigator);
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
someData: null,
otherData: null,
};
}
componentDidMount = () => {
this.setState({ someData: 'some data', otherData: 'other data' });
};
render = () => {
return (
<Authenticator>
<AppContainer
screenProps={{
someData: this.state.someData,
otherData: this.state.otherData,
}}
/>
</Authenticator>
);
};
}
export default App;
The code sample above skips a few important details; I have created a full Expo Snack.
The AWS Amplify Authenticator wraps the application and makes authentication data available to embedded components as e.g. this.props.authState; see docs.
My problem now is that I don't understand how to access this auth data in e.g. FirstTabScreen: In that component, this.props contains screenProps and navigation objects, but this.props.authState is undefined. With AppContainer and AppNavigator in between Authenticator and e.g. FirstTabScreen, I don't see how to pass auth data as props or transport them by some other means.
Furthermore, I just pass all data to all embedded components in screenProps; this does work, but not all screens require all data. How would I go about passing them only the data they actually require ? I know about passing parameters to routes, but all the code samples in there have a Button and use this.props.navigation.navigate(...) in the onPress handler. I don't have any buttons on those screens and don't need any - after all, navigating to a different tab is what the tabs in the bottom tab nav bar are for ?!?
Could anyone kindly shed some light on this for me ?
using screeProp is not encouraged on react navigation v3
I believe it's no longer encouraged to put navigation such as TabNavigator nested within a component. I'm not quite sure how to handle such cases gracefully in V3. I wanted to upgrade from V1 to V3 recently and gave up on the upgrade since I couldn't find enough info on what to do with the nested navigators I have.
another idea from esipavicius
do not wrap screens to stack navigator which include the tab navigator. Maybe you Can use navigationAction setState or setParams with params and navigator key. When You Can dispatch it. And automaticaly changes state with params to which scren you dispatched.
OPTION 1 - USING SCREEN PROP
Your option of passing screen props
class App extends React.Component {
componentDidMount = () => {
this.setState({ someData: 'some data', otherData: 'other data' });
};
render = () => {
return (
<Authenticator>
<AppContainer
screenProps={{ myProp: "test" }}
/>
</Authenticator>
);
};
}
OPTION 2 - USING THE REACT CONTEXT API
Using the Context api and wrapping it around your <AppContainer>.
class App extends React.Component {
render = () => {
return (
<FormContext.Provider value={this.state}>
<AppContainer />
</FormContext.Provider>
);
};
}
Then consume it in each one of your screens.
class FirstTabScreen extends React.Component {
render(){
return(
<FormContext.Consumer>
{ (context) => (
// your logic
)}
</FormContext.Consumer>
};
}
Option 3 - TAB NAVIGATOR OBJECT
You can pass a TabNavigatorObject
createBottomTabNavigator(RouteConfigs, BottomTabNavigatorConfig);
You can pass an extra option to the BottomTabNavigator in the BottomTabNavigatorConfig.
const BottomTabNavigator = StackNavigator(routesConfig, BottomTabNavigatorConfig)
The BottomTabNavigatorConfig is an option and you can check the api docs
{ initialRouteName: 'Home', navigationOptions: .... }
Option 4 - RETRIEVE PARAM FROM PARENT
as explained on github and in the api docs, you can retrieve the parent component and then use getParam.
const parent = navigation.dangerouslyGetParent();
const yourParam = parent.getParam('yourParamName', 'default value')
You can find many discussion about passing props with tab navigator on github
1) I would suggest to not use any logic like this outside navigator. If you want login, do something like:
const rootStack = createSwitchNavigator({
Login: { screen: LoginNavigator },
Main: createBottomTabNavigator({
FirstTab: { screen: FirstTabScreen },
SecondTab: { screen: SecondTabScreen },
});
})
It will give you more login possibilities and once you know user is loggedIn just navigate him to Main stack.
2) Use any store for app. For example react-navigation works great with redux. You can save all info there.
Try to not use screenProps for simple cases like this. Also you don't need to send authState to other screens, just save to store.
3) Once you want logout. Do: Auth.signOut(), clean store, and navigate to Login screen.
Try to follow these simple steps and you will be able to implement much more complicated things

React Navigation, Redux, How to change screens?

My routes are not triggering screen changes using the latest React Navigation v1.5.0.
I have integrated Redux in my setup as detailed in the react-navigation-redux docs. The biggest change I see here is the 'addListener' setup, although I'm not sure this is what is preventing the screen changes. My routes were working fine using v.1.0.
I see the navigation action being fired and the screen being added to the navigation state in the debugger, but the screen isn't changing.
Clicking on the button below dispatches the action, but the screen doesn't change to the About screen and stays on the Home screen.
RootNav
StackNavigator
- Home
- About
Index.js
const middleware = createReactNavigationReduxMiddleware(
"root",
state => state.navigationState,
)
const addListener = createReduxBoundAddListener("root");
class App extends Component {
render () {
return (
<View>
<RootNav navigation={addNavigationHelpers({
dispatch: this.props.dispatch,
state: this.props.navigationState,
addListener,
})} />
</View>
)
}
}
BUTTON
<TouchableHighlight onPress={ () =>
this.props.dispatch(NavigationActions.navigate({
routeName: 'About'
})) }>
<Text>About</Text>
</TouchableHighlight>
STATE AFTER CLICKING BUTTON:
nav: {
key: StackRouterRoot,
index: 1,
isTransitioning: true,
routes: [
0: {routeName: 'Home'},
1: {routeName: 'About'},
],
}
How do you properly dispatch route/screen changes?
Your navigation state is not bound to the redux, considering that you're using redux-navigation. You need to create a Navigation reducer and bind it to the store.
For eg
// Nav.js
// This is your exported router, which gets the initial State
import AppNavigator from '../Navigation/AppNavigation'
const initialState = AppNavigator.router.getStateForAction(AppNavigator.router.getActionForPathAndParams('LaunchScreen'))
export default reducer = (state = initialState, action) => {
const newState = AppNavigator.router.getStateForAction(action, state)
return newState || state
}
and bind it to your store like this
// Store.js
const RootReducer = combineReducers(config, {
nav: Nav,
// ...other reducers
});
and finally use it in your Redux Navigation state as
function ReduxNavigation (props) {
const addListener = createReduxBoundAddListener('root')
const { dispatch, nav } = props
const navigation = ReactNavigation.addNavigationHelpers({
dispatch,
state: nav, // this is bound to redux store using mapStateToProps
addListener
})
return <AppNavigation navigation={navigation} />
}
const mapStateToProps = state => ({ nav: state.nav })
export default connect(mapStateToProps)(ReduxNavigation)
as mentioned in the docs

React-navigation Redux State management

I'm learning React Native and Redux and I've started using 3rd party libraries - specifically React Navigation
I've followed a tutorial on it Dan Parker's Medium Tutorial and I still can't get it working
My RootContainer of the app:
<PrimaryNavigation />
...
export default connect(null, mapDispatchToProps)(RootContainer)
PrimaryNavigation definition:
const mapStateToProps = (state) => {
return {
navigationState: state.primaryNav
}
}
class PrimaryNavigation extends React.Component {
render() {
return (
<PrimaryNav
navigation={
addNavigationHelpers({
dispatch: this.props.dispatch,
state: this.props.navigationState
})
}
/>
)
}
}
export default connect(mapStateToProps)(PrimaryNavigation)
PrimaryNav definition:
const routeConfiguration = {
LoginScreen: {
screen: LoginScreen
},
MainContainer: {
screen: MainContainer
}
}
const PrimaryNav = StackNavigator(
routeConfiguration,
{
headerMode: 'none'
})
export default PrimaryNav
export const reducer = (state, action) => {
const newState = PrimaryNav.router.getStateForAction(action,state)
return newState || state;
}
My create store:
const rootReducer = combineReducers({
...
primaryNav: require('../Navigation/AppNavigation').reducer
})
return configureStore(rootReducer, rootSaga)
I get an error along the lines of:
Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. Check the render method of 'PrimaryNavigation'
My understanding so far is:
RootContainer is connected to a store - it holds a PrimaryNavigation
PrimaryNavigation contains a Navigator (PrimaryNav), it wraps the Navigator and passes it state
PrimaryNav is the actual Navigator - I've defined routes and default initializations
the reducer that handles PrimaryNav is just PrimaryNav.router.getStateForAction
Am I missing initial state? Am I not connecting it to Redux properly? Do I need to fire off a dispatch to go to the first screen?
Thanks
I think you are missing a few things in your code organization.
This is my effort to extend the github notetaker app done by Tyler Mcginnis. I updated the code to support latest versions of React and React Native, I also extended the app to use react-navigation supporting both iOS and Android. I added Redux to manage the store and used Redux-Sagas to handle asynchronous fetch of data.
https://github.com/ahmedkamohamed/react-native-navigation-redux-sagas
you can see full demo from my git : https://github.com/liuxiaocong/redux-with-react-navigation
use redux and react-navigation from react-native