× TypeError: Cannot read properties of undefined (reading 'getState')

Viewed 17477

I am a beginner learning react and redux. I wrote this demo about how to use connect.js in redux. Searching this kind of question but there is no right answer for my code. I got a undefined context. Is it typo? or I passed context in a wrong way? Thanks in advance. Here is my code.

index.js

import React from "react";
import ReactDOM from "react-dom";
import store from "./store";

import { Provider } from "react-redux";
import App from "./App";

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById("root")
);

/store/index.js

import { createStore } from "redux";
import reducer from "./reducer.js";
const store = createStore(reducer);
export default store;

/store/reducer.js

import { ADD, SUB, MUL, DIV } from './constants.js'

// or initialState
const defaultState = {
  counter: 0
}

function reducer(state = defaultState, action) {
  switch (action.type) {
    case ADD:
      return {...state, counter: state.counter + action.num};
    case SUB:
      return {...state, counter: state.counter - action.num};
    case MUL:
      return {...state, counter: state.counter * action.num};
    case DIV:
      return {...state, counter: state.counter / action.num};
    default:
      return state;
  }
}

export default reducer

connect.js

import React, { PureComponent } from "react";
import { StoreContext } from "./context";

export default function connect(mapStateToProps, mapDispatchToProps) {
  return function enhanceHOC(WrappedCpn) {
    class EnhanceCpn extends PureComponent {
      constructor(props, context) {
        super(props, context);
        console.log('connect props', props);
        console.log('connect context', context);  // context is undefined here
        this.state = {
          storeState: mapStateToProps(context.getState()),
        };
      }

      componentDidMount() {
        this.unSubscribe = this.context.subscribe(() => {
          this.setState({
            counter: mapStateToProps(this.context.getState()),
          });
        });
      }

      componentWillUnmount() {
        this.unSubscribe();
      }

      render() {
        return (
          <WrappedCpn
            {...this.props}
            {...mapStateToProps(this.context.getState())}
            {...mapDispatchToProps(this.context.dispatch)}
          />
        );
      }
    }
    EnhanceCpn.contextType = StoreContext;
    return EnhanceCpn;
  };
}

context.js

import React from "react";
const StoreContext = React.createContext();
export {
  StoreContext
}

App.js

import React, { PureComponent } from 'react'
import My from './pages/my'

export default class App extends PureComponent {
  constructor(props, context) {
    super(props, context);

    console.log('APP props', props);
    console.log('APP context', context); // context got value
  }

  render() {
    return (
      <div>
        <My />
      </div>
    )
  }
}

my.js

import React, { PureComponent } from 'react'
import { sub, mul } from '../store/actionCreators'
import connect from '../utils/connect'

class My extends PureComponent {

  render() {
    return (
      <div>
      <h3>my</h3>
      <h3>counter: { this.props.counter }</h3>
      <button onClick={e => this.props.subNum()}>-2</button>
      <button onClick={e => this.props.mulNUm(5)}>*5</button>
    </div>
    )
  }
}

const mapStateToProps = state => ({
  counter: state.counter
})

const mapDispatchToProps = dispatch => ({
  subNum: (num = -2) => {
    dispatch(sub(num))
  },
  mulNUm: num => {
    dispatch(mul(num))
  }

})

export default connect(mapStateToProps, mapDispatchToProps)(My)

actionCreators.js

import { ADD, SUB, MUL, DIV } from './constants.js'

export function add(num) {
  return {
    type: ADD,
    num
  }
}

export const sub = (num) => {
  return {
    type: SUB,
    num
  }
}

export const mul = (num) => ({
  type: MUL,
  num
})

export const div = num => ({
  type: DIV,
  num
})

constants.js

const ADD = 'ADD_ACTION'
const SUB = 'SUB_ACTION'
const MUL = 'MUL_ACTION'
const DIV = 'DIV_ACTION'

export { ADD, SUB, MUL, DIV }
4 Answers

From the docs, here is what it says with regards to Class.contextType:

The contextType property on a class can be assigned a Context object created by React.createContext(). Using this property lets you consume the nearest current value of that Context type using this.context. You can reference this in any of the lifecycle methods including the render function.

It seems that in your case, you are just missing passing your custom StoreContext to redux Provider with the context props

You need to do something like:

import React from "react";
import ReactDOM from "react-dom";
import store from "./store";
import { StoreContext } from "./context";

import { Provider } from "react-redux";
import App from "./App";

ReactDOM.render(
  <Provider store={store} context={StoreContext}>
    <App />
  </Provider>,
  document.getElementById("root")
);

See also https://react-redux.js.org/using-react-redux/accessing-store#providing-custom-context

I got the same issue and solved by creating store on the same file as the root component where Provider is applied. Example code below:

 <Provider store={createStore(reducers)}>
    <App />
 </Provider>

Cannot read properties of undefined (reading 'getState')

Solution:

(1) make a file store.js in your redux folder and you can copy the code

    import { createStore, applyMiddleware } from "redux";
    import logger from 'redux-logger';
    
    import rootReducer from "./root-reducer";
    
    const middlewares = [logger];
    
    const store = createStore(rootReducer, applyMiddleware(...middlewares));
    
    export default store;

(2) then just import the file in index.js file

     <Provider store={store}>
          <BrowserRouter>
             <App />
          </BrowserRouter>
      </Provider>

i got same issues i just have to change were i have my configure store from this

import { configureStore } from "@reduxjs/toolkit";
import basketReducer from "../slices/basketSlice";
export const store = configureStore({
  reducer: {
    basket: basketReducer,
       },
         });

to this

import { configureStore } from "@reduxjs/toolkit";
import basketReducer from "../slices/basketSlice";
export default configureStore({
 reducer: {
  basket: basketReducer,
    },
     });

and this method works for me i also find out when i play around with the code to understand why it happen and see if i broke it what will happen this is my finding and observation from it

note concerning redux if you used the below as import in your _app.js

import { store } from "../stores/store";

then the global store should be rewritten like this

 export const store = configureStore({
      reducer: {
        basket: basketReducer,
      },
    });

but if you import it like this with out distructuring it[store] or without the curly bracket

import store  from "../stores/store";

then you should write the store like this

 export default configureStore({
                reducer: {
                  basket: basketReducer,
                },
              });

Both ways works for me

Related