使用带有 react-navigation 的 redux store

Using a redux store with react-navigation

我正在尝试使用 expo 将 redux 存储添加到我的 react-native 应用程序。

应用程序如下所示:

import React from 'react';

// Persistent storage
import AsyncStorage from '@react-native-async-storage/async-storage';

// 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';

// Store
import { Provider } from 'react-redux';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import store from './store/store';
import { setNCServer, setToken } from './store/actions';


// 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 (
        <Provider store={store}>
          <NavigationContainer>
            <Stack.Navigator>
              <Stack.Screen name="Home" component={Home} />
              <Stack.Screen name="Login" component={Login} />
            </Stack.Navigator>
          </NavigationContainer>
        </Provider>
      ) 
    } else {
      return (
        <Provider store={store}>
          <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>
        </Provider>
      )
    }
  }
}

// Initialise store
const mapDispatchToProps = dispatch => (
  bindActionCreators({
    setNCServer,
    setToken,
  }, dispatch)
);
const mapStateToProps = (state) => {
  return state
};
const connector = connect(mapStateToProps, mapDispatchToProps)
export default connector(App);

问题:当我启动应用程序时,我收到臭名昭著的错误“错误:无法在“Connect(App)”的上下文中找到“store”。

我已经为这个问题绞尽脑汁好几个小时了,但找不到我做错了什么。

谁能帮帮我?

您只能在 Redux Provider 组件的 内部 组件上使用 connect。现在你的 ProviderApp 里面,所以你不能在 App 上使用 connect。您需要将 Provider 上移一个级别。

我在这里也看到了很多其他问题:this.props.state.setToken(token) 应该是 this.props.setToken(token) 并且 this.state.token 应该是 this.props.state.token -- 但实际上你应该是使用 mapStateToProps 只是 select 令牌而不是 return 整个状态!

您可能会发现 useSelectoruseDispatch 挂钩更直观。

url.url 这个名字很容易混淆。实际上,这是一个带有 属性 url 的事件,因此您应该将其命名为 eevent。但您也可以将其解构为 ({url}) 而不是 (e)e.url.

// Application contents
class AppNavigation extends React.Component {
  constructor(props) {
    super(props);

    // Retrieve token from storage if available
    AsyncStorage.getItem('token').then((token) => {
      this.props.setToken(token);
    });

    // Register handler to catch Nextcloud's redirect after successfull login
    Linking.addEventListener('url', this.handleRedirect);
  }

  // Function to retrieve the device's token and save it after user logged in
  handleRedirect = async ({ url }) => {
    if (url.startsWith('nc://login/server')) {
      try {
        const token = url.substring(url.lastIndexOf(':'));
        console.log('Persisting token', token);
        AsyncStorage.setItem('token', token);
        this.props.setToken(token);
      } catch (e) {
        // TODO
      }
    }
  };

  render() {
    if (this.props.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>
      );
    }
  }
}

// Can just be an object of functions to bind - creates props `setNCServer` and `setToken`
const mapDispatchToProps = {
  setNCServer,
  setToken,
};

// Creates a prop `token` with the token from state
const mapStateToProps = (state) => {
  return {
    token: state.token,
  };
};

const connector = connect(mapStateToProps, mapDispatchToProps);

const ConnectedNavigation = connector(AppNavigation);

// use the connected component inside a redux Provider
export default function App() {
  return (
    <Provider store={store}>
      <ConnectedNavigation />
    </Provider>
  );
}