Search code examples
reactjsreduxreact-reduxredux-toolkit

Dispatching causing TypeError 'listener2 is not a function"


I'm simply trying to dispatch an action to the store and am getting this error. I have no idea what listener2 is or why there's a TypeError:

listener2 is not a function
TypeError: listener2 is not a function
    at wrappedListener
    at Map.forEach (<anonymous>)
    at Object.dispatch
    at dispatch
    at App

This is my only slice:

import {createSlice} from '@reduxjs/toolkit';

const PostContainerSlice = createSlice({
  name: "postContainer",
  initialState: [],
  reducers: {
    addPost: (state, action) => {
      state.push(action.payload)
    },
  }
})

export const {addPost} = PostContainerSlice.actions;
export default PostContainerSlice.reducer;

store.js:

import {configureStore} from '@reduxjs/toolkit';
import PostContainerReducer from './features/post/PostContainerSlice.js';

export default configureStore({
  reducer: {
    postContainer: PostContainerReducer
  }
})


index.js:

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import store from './store.js';

const root = ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
      <App dispatch={store.dispatch} state={store.getState()} />
  </React.StrictMode>
);

store.subscribe(root);

App.js:

import './App.css';
import PostContainer from './features/post/PostContainer'

function App(props) {
  const {state, dispatch} = props;

  dispatch({
    type: 'postContainer/addPost',
    payload: 'test'
  })
  console.log(state)

 
}

export default App;

Console logging the store's state works fine, just dispatching is causing this. When removing 'store.subscribe(root)' from index.js the error doesn't happen, but console logging shows that nothing was dispatched and the store remains unchanged...maybe that has nothing to do with the issue. Also, when dispatching and logging state from index.js above the App rendering, the store appears to dispatch and log normally... thought this was a clue but has led me nowhere.


Solution

  • Issue

    The "listener" in question here is the listener callback function that store.subscribe expects to be passed.

    See subscribe(listener)

    ReactDOM.createRoot().render doesn't return a function.

    const root = ReactDOM.createRoot(document.getElementById('root')).render(
      <React.StrictMode>
          <App dispatch={store.dispatch} state={store.getState()} />
      </React.StrictMode>
    );
    
    store.subscribe(root); // root is not a function
    

    Solution

    You might be able to get by with creating a function that renders the app that can function as a listener callback as well.

    Example:

    const root = ReactDOM.createRoot(document.getElementById('root'));
    
    const renderApp = () => { 
      root.render(
        <React.StrictMode>
          <App dispatch={store.dispatch} state={store.getState()} />
        </React.StrictMode>
      );
    };
    
    renderApp(); // <-- call once to initially render app
    
    store.subscribe(renderApp); // <-- pass as listener to rerender with state changes
    

    Note: This is not how we use Redux in React though!

    You should import and wrap the App component in the react-redux Provider component to provide the Redux store context to the React app.

    import React from 'react';
    import ReactDOM from 'react-dom/client';
    import { Provider } from 'react-redux';
    import './index.css';
    import App from './App';
    import store from './store.js';
    
    const root = ReactDOM.createRoot(document.getElementById('root')).render(
      <React.StrictMode>
        <Provider store={store}>
          <App />
        </Provider>
      </React.StrictMode>
    );
    

    The App component, and all components in the sub-ReactTree, should import and use the useDispatch and useSelector hooks from react-redux to dispatch actions to the store, and subscribe to selected state updates.

    import { useEffect } from 'react';
    import { useDispatch, useSelector } from 'react-redux';
    import './App.css';
    import PostContainer from './features/post/PostContainer'
    
    function App() {
      const dispatch = useDispatch();
      const postContainer = useSelector(state => state.postContainer);
    
      useEffect(() => {
        dispatch({
          type: 'postContainer/addPost',
          payload: 'test'
        });
      }, [dispatch]);
    
      useEffect(() => {
        console.log({ postContainer });
      }, [postContainer]);
     
      ...
    }
    
    export default App;