首页 > 解决方案 > 使用带有 react-navigation 的 redux 存储

问题描述

我正在尝试使用 expo 在我的 react-native 应用程序中添加一个 redux 商店。

该应用程序如下所示:

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

问题:当我启动应用程序时,我收到臭名昭著的错误“错误:在“连接(应用程序)”的上下文中找不到“商店”。

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

任何人都可以帮助我吗?

标签: reactjsreact-nativereduxexporeact-navigation

解决方案


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

我在这里也看到了许多其他问题:this.props.state.setToken(token)应该是this.props.setToken(token)并且this.state.token应该是this.props.state.token——但实际上你应该使用它mapStateToProps来选择令牌而不是返回整个状态!

你可能会发现useSelectoranduseDispatch钩子更直观。

url.url名称非常混乱。真的这是一个带有属性的事件,url所以你应该调用它eevent. 但是您也可以将其解构为({url})代替(e)and 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>
  );
}

推荐阅读