2
votes

I am trying to dispatch another action inside one async action creator. However, when I am using dispatch function provided by redux-thunk middleware, it is throwing Uncaught TypeError: dispatch is not a function error.

Below are the files for more help

store/store.dev.js

use strict';

import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import rootReducer from '../reducers';
import ReduxThunk from 'redux-thunk';

export default function configureStore(initialState = {}) {
const store = createStore(rootReducer, initialState, applyMiddleware(ReduxThunk));
  console.log('store returned', store);
return store;
}

src/index.js

'use strict';

import React from 'react';
import { BrowserRouter } from 'react-router-dom';
import { hydrate } from 'react-dom';
import { Provider } from 'react-redux';
import { renderRoutes } from 'react-router-config';
import configureStore from './store';
import App from './containers/App.js';
import routes from './routes/index.js';

hydrate(
  <Provider store={configureStore()}>
    <BrowserRouter>
      {renderRoutes(routes)}
    </BrowserRouter>
  </Provider>,
  document.getElementById('root')
);

reducer

'use strict';

import { LOGIN_SUBMITTED, LOGIN_COMPLETED } from '../constants/ActionTypes.js';

const loginSubmitReducer = (state = [], action) => {
  console.log('in reducer');
    switch (action.type) {

      case LOGIN_SUBMITTED:
      console.log('login submitted case');
        return Object.assign({}, state, {
          loginSubmitted: true
        });

      case LOGIN_COMPLETED:
      console.log('login completed case');
        return Object.assign({}, state, {
          loginCompleted: true
        });

      default:
      console.log('in default case');
        return {
          a:1
        };
    }
}

export default loginSubmitReducer;

containers/App.js

'use strict';

import React from 'react';
import { loginCompleted, loginSubmitted } from '../actions';
import { createStructuredSelector, createSelector } from 'reselect';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { Header, ButtonDemo, LoginForm } from '../components';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import lightBaseTheme from 'material-ui/styles/baseThemes/lightBaseTheme';
import getMuiTheme from 'material-ui/styles/getMuiTheme';

class App extends React.Component {
  constructor(props, context) {
    super(props);
    this.state = {
      appState: {
        entry: 'yes'
      }
    }

    console.log('props in App constructor', props);
    console.log('context in App constructor', context);

    this.newFunc = this.newFunc.bind(this);
  }
  newFunc () {
    console.log('props', this.props);
  }
  render() {
    console.log('props in main App', this.props);
    console.log('Actions', this.props.actions);
    console.log('context in App', this.context);
    // this.props.actions.loginCompleted();
    const muiTheme = getMuiTheme({
      lightBaseTheme
    });

    return (
      <MuiThemeProvider muiTheme={muiTheme}>
        <div>
            <Header />
            <LoginForm 
              appstate = {this.props.appState}
              dispatchAction = {this.props.actions}
            />
        </div>
      </MuiThemeProvider>
    );
  }
}

// const mapStateToProps = createStructuredSelector({});
const mapStateToProps = (state) => {
  return { appState: state.loginSubmitReducer };
}

function mapDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators({ loginCompleted, loginSubmitted }, dispatch)
  };
  // dispatch(loginCompleted());
}

export default connect(mapStateToProps, mapDispatchToProps)(App);

components

    'use strict';

import React, { PropTypes } from 'react';
import TextField from 'material-ui/TextField';
import RaisedButton from 'material-ui/RaisedButton';
import FloatingActionButton from 'material-ui/FloatingActionButton';
import ContentAdd from 'material-ui/svg-icons/content/add';
import { loginAction } from '../actions';
// import '../css/style.css';


class LoginForm extends React.Component {
    constructor(props) {
        super(props);
        this.loginSubmit = this.loginSubmit.bind(this);
    }

    loginSubmit() {
        console.log('Login fomr submitted', this.props);
        console.log('loginAction', loginAction);
        loginAction()();
    }

    render () {
        console.log('props when login rendered', this.props);
        return (
            <div className="loginForm">
                <div className="title">LOGIN</div>
                <form>
                    <TextField 
                        hintText="Username"
                        floatingLabelText="Username"/><br/>
                    <br/>
                    <TextField
                        hintText="Password"
                        type="Password"
                        floatingLabelText="Password"/><br/>
                    <br/>
                    <RaisedButton
                        label="Login" 
                        secondary={true} 
                        onClick={this.loginSubmit}
                        className="submitBtn" /><br/>
                    <br/>
                    <FloatingActionButton                       
                        secondary={true}
                        className="registerBtn">
                        <ContentAdd />                      
                    </FloatingActionButton>
                </form>
            </div>
        )
    }
}

export default LoginForm;

actions/loginAction.js

'use strict';

// import { polyfill } from 'es6-promise';
// require('es6-promise').polyfill();
// require('isomorphic-fetch');
import request from 'superagent';
// import fetch from 'isomorphic-fetch';
import loginCompleted from './loginCompletedAction.js';
import loginSubmitted from './loginSubmittedAction.js';

const loginAction = (dispatch) => {
    console.log('validateUserLogin executed', this);
    console.log('validateUserLogin dispatch', dispatch);
    return (dispatch) => {
        // console.log('first return', dispatch);
        // this.props.dispatch(loginSubmitted());
        // this.props.dispatchAction.loginSubmitted();
        dispatch(loginSubmitted());

        request.get('http://localhost:3000/loginSubmit')
            .end((err, res) => {
                console.log('res', res);
                console.log('err', err);
                // this.props.dispatchAction.loginCompleted();
            });
    }
}

export default loginAction;

Folder structure

Project structure

I am using redux-thunk middleware that exposes dispatch function to async action creators. Still it doesn't work.

Please help!

1
Is this the line that is causing the error? loginAction()(); I think you need to get loginAction from props (populated by mapDispatchToProps()) instead of importing it in your component. When you import it directly, it doesn't get wrapped, so dispatch() is not passed into it. If it's not that line, please tell us which line causes the error! That's pretty important info.stone
@skypecakes -- dispatch(loginSubmitted()); in actions/loginAction.js is causing the error.Sourav
Okay, but is that called from loginAction()? If it is, you're clearly not passing in dispatch, but it's expecting you to.stone
@skypecakes-- Yes.. I am not passing. Let me check.Sourav
loginAction will work if you dispatch(loginAction()), and make the change suggested by @Sag1v. Usual way to do that is in mapDispatchToProps. Otherwise dispatch will not be defined. You may find it helpful to study how dispatch is passed around - it seems mysterious with all the arrow functions (well, it did to me) but there's a clear path.stone

1 Answers

2
votes

Your first argument of the action creator should not be the dispatch but the argument you pass to the action creator.

const loginAction = () => (dispatch) => {
    console.log('validateUserLogin executed', this);
    console.log('validateUserLogin dispatch', dispatch);
    // console.log('first return', dispatch);
    // this.props.dispatch(loginSubmitted());
    // this.props.dispatchAction.loginSubmitted();
    dispatch(loginSubmitted());

    request.get('http://localhost:3000/loginSubmit')
        .end((err, res) => {
            console.log('res', res);
            console.log('err', err);
            // this.props.dispatchAction.loginCompleted();
        });
}

Edit
As a follow up to your comment, iat first i thought you are not implementing the redux-thunk properly as a middle-ware but according to your code it looks ok.
Then I've noticed that you are not really dispatching loginAction but just invoking it.
You should dispatch it dispatch(loginAction()). Your component should be connected to redux then you will have access to dispatch.