最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - React : Action creator not calling reducer - Stack Overflow

programmeradmin0浏览0评论

I am making an async call in my action creator and calling my reducer with the result, but for some reason i could not fathom the reducer is not being called.

actions.js (action and action creator)

export const GET_FORMS = 'GET_FORMS'

export function getForms() {
  $.get("server url", function(result) {
   return {
    type: GET_FORMS,
    formlist: result.data.forms
   }   
  })
 }

reducers.js

import { GET_FORMS } from '../actions/actions'

export default function formAction(state = {forms:[]}, action) {
  console.log("received action"+action.type)
  switch (action.type) {

    case GET_FORMS:
      console.log("Action:"+action);
      return Object.assign({},state,{
       forms:action.formlist
      })

   default:
    return state
 }
}

App.js

import React, { Component, PropTypes } from 'react'
import ReactDOM from 'react-dom';
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import auth from '../auth'
import FormTable from '../components/FormTable'
import * as FormActions from '../actions/actions'


class App extends Component {

 constructor(props) {
  super(props);
  this.state = {forms: []};
 }

 componentDidMount() {
  // for some reason if i write this.props.actions.getForms () i get an error
  // Uncaught Error: Actions must be plain objects. Use custom middleware for 
  // async actions.
  FormActions.getForms();
 }

 render() {
  const {forms,actions} = this.props
  console.log(forms);
  return (
    <FormTable results={forms} actions = {actions} /> 
  )
 }
}

App.PropTypes = {
  forms: PropTypes.array.isRequired
}

function mapStateToProps() {
 const {
  forms:forms
 } = {forms:[]}

 return {
  forms
 }
}

function mapDispatchToProps(dispatch) {
 return {
  actions: bindActionCreators(FormActions, dispatch)
 }
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(App)

I am making an async call in my action creator and calling my reducer with the result, but for some reason i could not fathom the reducer is not being called.

actions.js (action and action creator)

export const GET_FORMS = 'GET_FORMS'

export function getForms() {
  $.get("server url", function(result) {
   return {
    type: GET_FORMS,
    formlist: result.data.forms
   }   
  })
 }

reducers.js

import { GET_FORMS } from '../actions/actions'

export default function formAction(state = {forms:[]}, action) {
  console.log("received action"+action.type)
  switch (action.type) {

    case GET_FORMS:
      console.log("Action:"+action);
      return Object.assign({},state,{
       forms:action.formlist
      })

   default:
    return state
 }
}

App.js

import React, { Component, PropTypes } from 'react'
import ReactDOM from 'react-dom';
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import auth from '../auth'
import FormTable from '../components/FormTable'
import * as FormActions from '../actions/actions'


class App extends Component {

 constructor(props) {
  super(props);
  this.state = {forms: []};
 }

 componentDidMount() {
  // for some reason if i write this.props.actions.getForms () i get an error
  // Uncaught Error: Actions must be plain objects. Use custom middleware for 
  // async actions.
  FormActions.getForms();
 }

 render() {
  const {forms,actions} = this.props
  console.log(forms);
  return (
    <FormTable results={forms} actions = {actions} /> 
  )
 }
}

App.PropTypes = {
  forms: PropTypes.array.isRequired
}

function mapStateToProps() {
 const {
  forms:forms
 } = {forms:[]}

 return {
  forms
 }
}

function mapDispatchToProps(dispatch) {
 return {
  actions: bindActionCreators(FormActions, dispatch)
 }
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(App)
Share Improve this question asked Nov 19, 2015 at 16:29 Kumar RKumar R 4714 gold badges6 silver badges16 bronze badges
Add a comment  | 

2 Answers 2

Reset to default 13

There are a few things here.

  1. Your action creator doesn't return the action that it's creating. $.get does not return an action, so mapping it to dispatch isn't productive.

    Reasonably, this may lead confused about how to return an action from an async action creator. Since it's async, by nature it can't return the final result (unless you're using es7 async await). The pattern you're attempting is identical to this:

    class App extends Component {
      ...
      componentDidMount() {
        // Instead of calling getForms, do the same directly
        $.get(url, function(res) {
          return { type: GET_FORMS, forms: res };
        });
      }
    
      render() { ... }
    }
    

    From the $.get api we know that $.get returns a jqXHR object , not any kind of action. So you have to use the result in the callback its self--you can't just return it. Here's an example of what componentDidMount could then look like (note the callback is bound to this):

    componentDidMount() {
      $.get(url, function(res) {
        this.props.dispatch({ type: GET_FORMS, forms: res });
      }.bind(this))
    }
    
  2. Doing async in your component or action creator is an anti-pattern, or at least discouraged. In this case, you're defining the async inside an action creator, which is great. But you're executing the async operation as well, which is bad. Instead you should employ some kind of middleware to handle async operations. Middleware are just functions that take actions and do stuff with them after they are dispatched, and the most common one for this purpose is redux-thunk. Using redux-thunk, you simply return a function that accepts dispatch as an argument. In this case, your action creator would look like this:

    export function getForms() {
      return function(dispatch) {
        $.get(url, function(result) {
          dispatch({ type: GET_FORMS, forms: result });
        })
      }
    }
    

    This is very similar to what you're already doing, except that you're creating a thunk--which is just a function that defines another function for execution later--instead of actually doing the async operation now, you're defining it for execution later.

  3. You're not actually dispatching anything.

    The simplest problem to fix, but definitely a barrier :) In componentDidMount, you're calling the action creator that you've imported, but you're not calling dispatch. You're 50% of the way there though, in that you passed the action creators to connect, but even though you did that, you didn't call the bound action creators that connect passes into prop--you called the unbound ones.

    So instead of calling FormActions.getForms(), you need to call this.props.actions.formActions(). The former is not bound, but the latter is. Calling the latter is the same as doing this.props.dispatch(FormActions.getForms()).

  4. And finally: you don't need to define mapDispatchToProps in this case. You can pass objects to that parameter of connect. See below:

    // Instead of this
    export default connect(mapStateToProps, mapDispatchToProps)(App);
    
    // do this:
    export default connect(mapStateToProps, FormActions)(App);
    // creates the bound action creator 'this.props.getForms()'
    

    This one is mostly a style choice, but I think this is a good pattern :) For multiple action creator source objects, you can do this:

    export default connect(mapStateToProps, { ...FormActions, ...UserActions })(App);
    

You should use the redux-thunk middle ware: https://github.com/gaearon/redux-thunk

essentially as your code stands - you aren't returning an FSA compliant action (and actually aren't returning anything right now). You need to thunk the dispatch and then return the result of the promise.

More on async action creators here: http://redux.js.org/docs/advanced/AsyncActions.html

发布评论

评论列表(0)

  1. 暂无评论