I'm currently following this article. I'm currently encountering an error in the TabBarNavigation. From what I could understand the problem could be on the connect method when I export the class. However I have wrapped this in the <Provider store={store}> <TabBarNavigation/> </Provider>.
This is the code where I'm getting the error:
import React, { Component } from 'react';
import { addNavigationHelpers } from 'react-navigation';
import { TabBar } from '../navigationConfiguration';
import { connect } from 'react-redux';
import { View } from 'react-native';
const mapStateToProps = (state) => {
return {
navigationState: state.tabBar,
}
}
class TabBarNavigation extends Component {
render(){
const { dispatch, navigationState } = this.props
return (
<TabBar
navigation={
addNavigationHelpers({
dispatch: dispatch,
state: navigationState,
})
}
/>
)
}
}
export default connect(mapStateToProps)(TabBarNavigation)
And this is the error
TypeError: undefined is not a function (evaluating 'addListener')
This error is located at:
in withCachedChildNavigation(TabView) (at TabNavigator.js:34)
in Unknown (at createNavigator.js:13)
in Navigator (at createNavigationContainer.js:226)
in NavigationContainer (at TabNavigation.js:18)
in TabBarNavigation (created by Connect(TabBarNavigation))
in Connect(TabBarNavigation) (at index.js:15)
in Provider (at index.js:14)
in SampleNavigation (at renderApplication.js:35)
in RCTView (at View.js:78)
in View (at AppContainer.js:102)
in RCTView (at View.js:78)
in View (at AppContainer.js:122)
in AppContainer (at renderApplication.js:34)
<unknown>
getChildEventSubscriber.js:60:16
getChildEventSubscriber
getChildEventSubscriber.js:59:49
<unknown>
withCachedChildNavigation.js:53:12
_updateNavigationProps
withCachedChildNavigation.js:45:38
componentWillMount
withCachedChildNavigation.js:16:34
callComponentWillMount
NativeModules.js:59:2
mountClassInstance
NativeModules.js:150:13
updateClassComponent
ReactNativeRenderer-dev.js:863:3
beginWork
ReactNativeRenderer-dev.js:1563:9
performUnitOfWork
ReactNativeRenderer-dev.js:4726:9
workLoop
ReactNativeRenderer-dev.js:4782:5
invokeGuardedCallback
react.development.js:1163:12
invokeGuardedCallback
react.development.js:1246:4
renderRoot
ReactNativeRenderer-dev.js:4828:6
performWorkOnRoot
performWork
ReactNativeRenderer-dev.js:5565:21
performSyncWork
ReactNativeRenderer-dev.js:5546:9
requestWork
ReactNativeRenderer-dev.js:5457:16
scheduleWorkImpl
ReactNativeRenderer-dev.js:5265:9
scheduleWork
ReactNativeRenderer-dev.js:5208:2
scheduleRootUpdate
ReactNativeRenderer-dev.js:5997:6
updateContainerAtExpirationTime
ReactNativeRenderer-dev.js:6038:2
updateContainer
ReactNativeRenderer-dev.js:6063:2
render
renderApplication
renderApplication.js:58:21
run
AppRegistry.js:104:10
runApplication
AppRegistry.js:196:26
__callFunction
react.development.js:684:1
<unknown>
react.development.js:377:9
__guardSafe
react.development.js:633
callFunctionReturnFlushedQueue
react.development.js:376:9
The code of the TabBar component:
import { TabNavigator } from 'react-navigation';
import TabOneNavigation from '../tabOne/views/TabOneNavigation'
import TabTwoNavigation from '../tabTwo/views/TabTwoNavigation';
const routeConfiguration = {
TabOneNavigation: { screen: TabOneNavigation },
TabTwoNavigation: { screen: TabTwoNavigation },
}
const tabBarConfiguration = {
tabBarOptions: {
activeTintColor: 'white',
inactiveTintColor: 'blue',
activeBackgroundColor: 'blue',
inactiveBackgroundColor: 'white',
}
}
Code of my Store:
import { applyMiddleware, combineReducers, createStore, compose } from 'redux';
import { createLogger } from 'redux-logger';
import thunkMiddleware from 'redux-thunk';
// Navigation Configs
import { NavigatorTabOne } from './tabOne/navigationConfiguration';
import { NavigatorTabTwo } from './tabTwo/navigationConfiguration';
import { TabBar } from './tabBar/navigationConfiguration';
// Middleware
const loggerMiddleware = createLogger({ predicate: (getState, action) => __DEV__ });
function configureStore(initialState) {
const enhancer = compose(
applyMiddleware(
thunkMiddleware, // lets us dispatch() functions
loggerMiddleware,
),
);
return createStore(
combineReducers({
tabBar: (state, action) => TabBar.router.getStateForAction(action, state),
tabOne: (state, action) => NavigatorTabOne.router.getStateForAction(action, state),
tabTwo: (state, action) => NavigatorTabTwo.router.getStateForAction(action, state),
}),
initialState,
enhancer
);
}
export const store = configureStore({})
export const TabBar = TabNavigator(routeConfiguration, tabBarConfiguration);
How can I fix this?
The redux integration has changed.
Check following document.
https://reactnavigation.org/docs/redux-integration.html
Related
I'm developing a react-native app with expo.
I've reach a point when some shared store is needed between my components, so I turned to redux.
But, after a few hours struggling, I could not get it working: I keep getting the infamous error "Error: Could not find "store" in the context of "Connect(AppProvider)"".
However, everything looks fine to me. I don't understand where the error comes from. Does anyone have a clue what I'm doing wrong?
Here's my index.js file:
import React from 'react'
import { Provider } from 'react-redux'
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux'
import App from './App.js'
import store from './store/store.js'
import { setNCServer, setToken } from './store/actions';
import { registerRootComponent } from 'expo';
const AppProvider = () => {
return (
<Provider store={store}>
<App />
</Provider>
)
}
// Initialise store
const mapDispatchToProps = dispatch => (
bindActionCreators({
setNCServer,
setToken,
}, dispatch)
);
const mapStateToProps = (state) => {
return state
};
const connector = connect(mapStateToProps, mapDispatchToProps)
const AppRoot = connector(AppProvider);
export default registerRootComponent(AppRoot);
Here is my store.js file:
import { createStore } from 'redux';
import CombinedReducers from './reducers.js';
const store = createStore(CombinedReducers);
export default store
Here is my reducers.js file:
import { combineReducers } from 'redux';
const tokenReducer = (state = null, action) => {
switch (action.type) {
case 'set':
return action.payload;
default:
return state
}
};
const NCServerReducer = (state = null, action) => {
switch (action.type) {
case 'set':
return action.payload;
default:
return state
}
};
export default combinedReducers({
NCServer: NCServerReducer,
token: tokenReducer
});
And here's my action.js file:
export const setNCServer = NCServer => (
{
type: 'set',
payload: NCServer,
}
);
export const setToken = token => (
{
type: 'set',
payload: token,
}
);
======== UPDATE =========
Following Pera's answer (https://stackoverflow.com/a/67746329/16063826), I've tried to move my connect logic in the App component.
Not really sure how to do that, my index.js file now looks like this:
import React from 'react';
import { Provider } from 'react-redux' ;
import App from './App.js';
import store from './store/store.js';
export default class AppProvider extends React.Component {
render() {
return (
<Provider store={store}>
<App />
</Provider>
)
}
}
And my App.js file, like this:
import React from 'react';
// Persistent storage
import AsyncStorage from '#react-native-async-storage/async-storage';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux'
import { setNCServer, setToken } from './store/actions';
import { registerRootComponent } from 'expo';
// Navigation
import { NavigationContainer } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
import Login from './Login';
import Home from './Home';
import BoardScreen from './BoardScreen';
import BoardDetailsScreen from './BoardDetailsScreen';
import CardDetailsScreen from './CardDetailsScreen';
// For creating an URL handler to retrieve the device token
import * as Linking from 'expo-linking';
// Create Stack navigator
const Stack = createStackNavigator()
// Application
class App extends React.Component {
constructor(props) {
super(props)
// Retrieve token from storage if available
AsyncStorage.getItem('token').then(token => {
this.props.state.setToken(token)
})
// Register handler to catch Nextcloud's redirect after successfull login
Linking.addEventListener('url', (url) => {this.handleRedirect(url)})
}
// Function to retrieve the device's token and save it after user logged in
handleRedirect = async (url) => {
if (url.url.startsWith('nc://login/server')) {
try {
token = url.url.substring(url.url.lastIndexOf(':'))
console.log('Persisting token', token)
AsyncStorage.setItem('token', token);
this.props.state.setToken(token)
} catch (e) {
// TODO
}
}
}
render() {
if (this.state.token === null) {
// No token is stored yet, we need to get one
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Home" component={Home} />
<Stack.Screen name="Login" component={Login} />
</Stack.Navigator>
</NavigationContainer>
)
} else {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="AllBoard" component={BoardScreen} options={{title: 'All boards'}} />
<Stack.Screen name="BoardDetails" component={BoardDetailsScreen} options={{title: 'Board details'}} />
<Stack.Screen name="CardDetails" component={CardDetailsScreen} options={{title: 'Card details'}} />
</Stack.Navigator>
</NavigationContainer>
)
}
}
}
// Initialise store
const mapDispatchToProps = dispatch => (
bindActionCreators({
setNCServer,
setToken,
}, dispatch)
);
const mapStateToProps = (state) => {
return state
};
const connector = connect(mapStateToProps, mapDispatchToProps)
const AppRoot = connector(App);
export default registerRootComponent(AppRoot);
But I'm still getting the same error.
The error logs look a bit strange to me:
Info Before connect
13:04
Info After connect
13:04
Info before createStore CombinedReducers: [Function combination]
13:04
Info after createStore
13:04
Info Running application on Mon iPhone.
13:04
Error Error: Could not find "store" in the context of "Connect(App)". Either wrap the root component in a <Provider>, or pass a custom React context provider to <Provider> and the corresponding React context consumer to Connect(App) in connect options.
13:04
This error is located at:
in Connect(App) (created by ExpoRoot)
in ExpoRoot (at renderApplication.js:45)
in RCTView (at View.js:34)
in View (at AppContainer.js:106)
in DevAppContainer (at AppContainer.js:121)
in RCTView (at View.js:34)
in View (at AppContainer.js:132)
in AppContainer (at renderApplication.js:39)
So, when I start the app, it goes over the connect and the createStore functions and fails later in ExpoRoot.... What could be going wrong here?
The error is that you are trying to use connect before actually initializing the Redux context with the store.
Try using connect with the App component instead, that way AppProvider has already rendered and the store is setup.
I've figured it out: the main problem was that export default registerRootComponent(); must be in index.js
The warning doesn't happen during debug or release mode builds but when testing App.js:
App.js
import React from 'react';
import { View, AppRegistry } from 'react-native';
import { gestureHandlerRootHOC } from 'react-native-gesture-handler';
import { LocalizationProvider } from 'library/localization';
import NavigationService from 'library/NavigationService';
import AppNavigator from 'library/AppNavigator';
const App = () => {
return (
<>
<LocalizationProvider>
<AppNavigator
ref={(navigatorRef) => {
NavigationService.setTopLevelNavigator(navigatorRef);
}}
/>
</LocalizationProvider>
</>
);
};
AppRegistry.registerComponent(appName, () => gestureHandlerRootHOC(App));
export default App;
the error doesn't appear if I remove the ref from AppNavigator.
AppNavigator.js
const guestUserNavigation = createStackNavigator(
{
Login: {
screen: LoginScreen
},
Logout: {
screen: LogoutActivity
}
},
{
headerMode: 'none',
initialRouteName: 'Login'
}
);
const userNavigation = createStackNavigator(
{
Home: {
screen: HomeScreen
}
},
{
headerMode: 'none',
initialRouteName: 'Home'
}
);
const App = createSwitchNavigator({
loader: {
screen: AuthLoadingScreen
},
Auth: {
screen: guestUserNavigation
},
App: {
screen: userNavigation
}
});
const AppNavigator = createAppContainer(App, {
initialRouteName: 'loader'
});
export default AppNavigator;
App.test.js
import React from 'react';
import { render } from 'react-native-testing-library';
import App from './App';
describe('App', () => {
it('should render the App', () => {
const result = render(<App />).toJSON();
expect(result).toMatchSnapshot();
});
});
mocks/react-navigation.js
jest.mock('react-navigation', () => ({
createAppContainer: jest
.fn()
.mockReturnValue(function NavigationContainer(props) {
return null;
}),
createSwitchNavigator: jest.fn(),
NavigationActions: {
navigate: jest.fn().mockImplementation((x) => x)
}
}));
now the test passes with the following warning that is bugging me and I cant seem to get it to work:
Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?
Check the render method of `App`.
in NavigationContainer (at App.js:29)
in LocalizationProvider (at App.js:28)
in App (at App.test.js:7)
using forwardRef works but then the app doesn't render.
AppNavigator shouldn't return null because this makes the mock useless to test App.
In order to receive a ref, AppNavigator should be class or forwardRef component. createAppContainer arguments can be optionally represented in element hierarchy in order to be testable by a snapshot:
...
createAppContainer: jest.fn((Comp, options) => React.forwardRef((props, ref) => {
return <div data-testid="AppContainer" ref={ref}>
<div data-testid="createAppContainer component">
{Comp.name}
</div>
<div data-testid="createAppContainer options">
{JSON.stringify(options)}
</div>
{children}
</div>;
})),
...
I am fairly new to react-native and have been hitting the following error while trying to set up my appNavigation along with redux.
TypeError: (0, _reactNavigation.addNavigationHelpers) is not a function. (In '(0, _reactNavigation.addNavigationHelpers)({dispatch:dispatch, state: nav})' (0, _reactNavigation.addNavigationHelpers)' is undefined.
Code -->
App.js -
import React from 'react';
import store from './src/store';
import { Provider } from 'react-redux';
import 'react-native-gesture-handler';
import AppNavigation from './src/components/AppNavigator';
export default class App extends React.Component {
render() {
return (
<Provider store={store}>
<AppNavigation />
</Provider>
);
}
}
AppNavigator.js -
import React from 'react';
import TodoApp from '../TodoApp';
import TodoDetails from '../components/TodoDetails';
import { connect } from 'react-redux';
import { addNavigationHelpers } from 'react-navigation';
import { createStackNavigator } from 'react-navigation-stack';
export const AppNavigator = createStackNavigator(
{
todoDetails: TodoDetails,
home: TodoApp,
},
{
initialRouteName: 'todoDetails',
},
);
const AppNavigation = ({ dispatch, nav }) => (
<AppNavigator navigation={addNavigationHelpers({ dispatch, state: nav })} />
);
const mapStateToProps = state => ({
nav :state.nav,
});
export default connect(mapStateToProps)(AppNavigation);
EDIT -
I did follow the steps given at https://stackoverflow.com/questions/50246457/react-native-reactnavigation-addnavigationhelpers-in-not-a-function
But unfortunately, that gives an error saying
TypeError: undefined is not an object (evaluating 'state.routes')
The error is located at:
in Navigator (at AppNavigator.js:20)
in AppNavigation (created by ConnectFunction)
in ConnectFunction (at App.js:19)......
addNavigationHelpers has been removed from react-navigation's default exports,
you need to directly add to the navigation props as
<AppNavigator navigation={{
dispatch: this.props.dispatch,
state: this.props.nav,
addListener,
}} />
A great answer by pritish , hope it help.s
I'am using react navigation with redux and after redux integration, i got some errors on drawer close.
import React from "react";
. . .
import { NavigationActions } from "react-navigation";
import { StackNavigator, DrawerNavigator } from 'react-navigation';
import { addListener } from "./components/common/utils";
import Dashboard from './components/pages/Dashboard';
. . .
const MainNavigator = StackNavigator({
Dashboard : {
screen : Dashboard,
},
. . .
})
export const AppNavigator = DrawerNavigator(
{
Main: { screen: MainNavigator }
}, {
contentComponent: Menu,
drawerWidth: 300,
headerMode: 'screen',
drawerPosition: 'left',
drawerOpenRoute: 'DrawerOpen',
drawerCloseRoute: 'DrawerClose',
drawerToggleRoute: 'DrawerToggle',
}
)
class AppWithNavigationState extends React.Component {
constructor (props) {
super(props)
this.onBackPress = this.onBackPress.bind(this)
}
componentDidMount () {
BackHandler.addEventListener('hardwareBackPress', this.onBackPress)
}
componentWillUnmount () {
BackHandler.removeEventListener('hardwareBackPress', this.onBackPress)
}
onBackPress () {
...
}
render() {
const { dispatch, nav } = this.props;
return (
<AppNavigator
navigation={{
dispatch,
state: nav,
addListener,
}}
/>
);
}
}
const mapStateToProps = state => ({
nav: state.nav,
});
AppWithNavigationState.propTypes = {
dispatch: PropTypes.func.isRequired,
nav: PropTypes.object.isRequired,
};
export default connect(mapStateToProps)(AppWithNavigationState);
Here is my reducer:
import { fromJS } from 'immutable';
import { NavigationActions } from "react-navigation";
import { combineReducers } from "redux";
import { AppNavigator } from "../../App";
import {...} from './constants';
import { ToastAndroid } from 'react-native';
const mainAction = AppNavigator.router.getActionForPathAndParams('Main');
const initialNavState = AppNavigator.router.getStateForAction(mainAction);
function nav(state = initialNavState, action) {
let nextState;
switch (action.type) {
case 'Reports':
nextState = AppNavigator.router.getStateForAction(
NavigationActions.back(),
state
);
break;
default:
nextState = AppNavigator.router.getStateForAction(action, state);
break;
}
return nextState || state;
}
const initialState = fromJS({
isLoading: true,
...
});
function store(state = initialState, action) {
switch (action.type) {
case SET_IS_LOADING:
return state.set('isLoading', action.value);
...
default:
return state;
}
}
const AppReducer = combineReducers({
nav,
store,
});
export default AppReducer;
and file i call DraweOpen:
import React from "react";
import PropTypes from "prop-types";
import { TouchableOpacity } from "react-native";
import { createStructuredSelector } from 'reselect';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import Icon from "react-native-vector-icons/dist/MaterialIcons";
import { DrawerBurger } from "../common/styles";
import { navigate } from "../store/actions";
const drawerButton = (props) => (
<DrawerBurger>
<TouchableOpacity
onPress={() => props.navigate("DrawerOpen")}
>
<Icon name="menu" size={30} color="white" />
</TouchableOpacity>
</DrawerBurger>
);
drawerButton.propTypes = {
navigate: PropTypes.func.isRequired,
};
const mapStateToProps = createStructuredSelector({});
const mapDispatchToProps = dispatch => (
(
bindActionCreators({
navigate,
}, dispatch)
)
);
export default connect(mapStateToProps, mapDispatchToProps)(drawerButton);
and i call drawerButton component on navigation options like:
...
class Dashboard extends Component {
static navigationOptions = () => ({
headerTitle: <Header dashboard />,
headerStyle: { backgroundColor: '#2c4e0f' },
headerLeft: <DrawerButton />,
});
...
I followed instructions on reactnavigation.org, also read some example code to build navigator.
Actually there was no error before redux integration and the navigator structure was same except BackHandling.
Here is my actions.js:
import { NavigationActions } from "react-navigation";
import {...} from './constants';
...
export const navigate = routeName => NavigationActions.navigate({ routeName });
My environment is:
react-navigation: 1.5.11
react-native: 0.53.0
react-navigation-redux-helpers: 1.0.5
react-redux: 5.0.7
redux: 3.7.2
node: 8.9.4
npm: 5.6.0
Thank for your help.
According to the redux integration docs, it seems you've missed one step.
You need to add addNavigationHelpers from React Navigation
Usage
import {addNavigationHelpers} from 'react-navigation';
<AppNavigator navigation={addNavigationHelpers({
dispatch,
state: nav,
addListener,
})} />
I have followed this great tutorial which is Tab Bar with three tabs using redux. Everything works great. Now I am trying to nest this Tab Bar inside Stack Navigator but I have the following error:
I am new to Redux and really cannot find where is the problem. Here is my code:
StackNav.js
import React from 'react';
import { connect } from 'react-redux';
import { addNavigationHelpers } from 'react-navigation';
import { RootNav } from './../navigationConfiguration';
const mapStateToProps = (state) => {
return { navigationState: state.nav };
};
class StackNav extends React.Component {
render() {
const { dispatch, navigationState } = this.props;
return (
<RootNav
navigation={
addNavigationHelpers({
dispatch,
state: navigationState,
})
}
/>
);
}
}
export default connect(mapStateToProps)(StackNav);
StackNav's navigationConfiguration.js
import { StackNavigator } from 'react-navigation';
import TabBarNavigation from './../tabBar/views/TabBarNavigation';
import Welcome from './../../Screens/Register/Welcome.js';
const routeConfiguration = {
Welcome: { screen: Welcome },
Home: { screen: TabBarNavigation },
};
const stackNavigatorConfiguration = {
initialRouteName: 'Welcome',
headerMode: 'screen',
navigationOptions: {
header: { visible: false }
}
};
export const RootNav = StackNavigator(routeConfiguration, stackNavigatorConfiguration);
Reducers
import { combineReducers } from 'redux';
// Navigation
import { AppNavigator } from './../stackNav/navigationConfiguration';
import { NavigatorTabOne } from './../tabOne/navigationConfiguration';
import { NavigatorTabTwo } from './../tabTwo/navigationConfiguration';
import { NavigatorTabThree } from './../tabThree/navigationConfiguration';
export default combineReducers({
nav: (state, action) => AppNavigator.router.getStateForAction(action, state),
tabOne: (state, action) => NavigatorTabOne.router.getStateForAction(action, state),
tabTwo: (state, action) => NavigatorTabTwo.router.getStateForAction(action, state),
tabThree: (state, action) => NavigatorTabThree.router.getStateForAction(action, state),
});
I also tried with this reducer instead nav: above
import { AppNavigator } from './../stackNav/navigationConfiguration';
const initialState = AppNavigator.router.getStateForAction(AppNavigator.router.getActionForPathAndParams('Welcome'));
export const navReducer = (state = initialState, action) => {
const nextState = AppNavigator.router.getStateForAction(action, state);
return nextState || state;
};
Start point of the app:
import React from 'react';
import {
AppRegistry,
Text
} from 'react-native';
import { Provider } from 'react-redux';
import StackNav from './../App/stackNav/views/StackNav';
import store from './store';
Text.defaultProps.allowFontScaling = false;
class App extends React.Component {
render() {
return (
<Provider store={store}>
<StackNav />
</Provider>
);
}
}
AppRegistry.registerComponent('MyApp', () => App);
I will appreciate any help. Thank you in advanced!
well, you're importing AppNavigator when you shoould be importing { RootNav } in reducer index
I don't have a direct answer to your question, but I can offer an example of how to nest Tab Navigators in Stack Navigators in this tutorial - https://developerlife.com/2017/04/15/navigation-and-styling-with-react-native/
Here's the JS class (from the tutorial and it's GitHub repo) that sets up the navigators and nesting - https://github.com/r3bl-alliance/react-native-weather/blob/master/app/Router.js