Search code examples
javascriptreactjsreact-nativereduxreact-redux

Cannot read properties of null (reading 'store')


I am learning react-native with redux. I am getting this error (seems the store is null but where?) when I am trying to access the state with useSelector.

Error:
Cannot read properties of null (reading 'store')
TypeError: Cannot read properties of null (reading 'store')
    at eval (react-redux.js:3:6325)
    at App (App.js.js:27:33
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import { useSelector, useDispatch, Provider } from 'react-redux';
import { createStore } from 'redux';
import Constants from 'expo-constants';

// You can import from local files
import AssetExample from './components/AssetExample';

// or any pure javascript modules available in npm
import { Card } from 'react-native-paper';

const initialState = { counter: 0 };
const counterReducer = (state = initialState, action) => {
  const { type, payload } = action;

  if (type == 'INCREMENT') {
    return { ...state, counter: state.counter++ };
  }
  return state;
};
const store = createStore(counterReducer);

export default function App() {
  const counter = useSelector((state) => state.counter);

  return (
    <Provider store={store}>
      <View style={styles.container}>
        <TouchableOpacity onPress={useDispatch({ type: 'INCREMENT' })}>
          <Text style={styles.paragraph}>{counter}</Text>
        </TouchableOpacity>

        <Card>
          <AssetExample />
        </Card>
      </View>
    </Provider>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    paddingTop: Constants.statusBarHeight,
    backgroundColor: '#ecf0f1',
    padding: 8,
  },
  paragraph: {
    margin: 24,
    fontSize: 18,
    fontWeight: 'bold',
    textAlign: 'center',
  },
});

Link to the code: https://snack.expo.dev/@iosdose/bba0eb


Solution

  • Issues

    • The App component is trying to use the useSelector hook outside the Redux Provider component providing the store object.
    • The TouchableOpacity component's onPress event handler is trying to directly use the useDispatch hook.
    • The counterReducer is mutating the counter state, i.e. state.counter++ mutates the reference.

    Solution

    Abstract the counter state and UI into a new component that is rendered within the Provider component's sub-ReactTree. Refactor to correctly use the useDispatch hook and correctly dispatch the INCREMENT action. Update the INCREMENT reducer case to correctly return a new state value and not mutate any of the existing state.

    Example:

    const initialState = { counter: 0 };
    const counterReducer = (state = initialState, action) => {
      const { type, payload } = action;
    
      if (type == 'INCREMENT') {
        return {
          ...state,
          counter: state.counter + 1 // <-- don't mutate!
        };
      }
      return state;
    };
    
    const Counter = () => {
      const dispatch = useDispatch();
      const counter = useSelector((state) => state.counter);
      return (
        <View style={styles.container}>
          <TouchableOpacity onPress={() => dispatch({ type: 'INCREMENT' })}>
            <Text style={styles.paragraph}>{counter}</Text>
          </TouchableOpacity>
    
          <Card>
            <AssetExample />
          </Card>
        </View>
      );
    };
    
    export default function App() {
      return (
        <Provider store={store}>
          <Counter />
        </Provider>
      );
    }
    

    Updated Expo Snack