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
2 Answers
Reset to default 13There are a few things here.
Your action creator doesn't return the action that it's creating.
$.get
does not return an action, so mapping it todispatch
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 ajqXHR
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 whatcomponentDidMount
could then look like (note the callback is bound tothis
):componentDidMount() { $.get(url, function(res) { this.props.dispatch({ type: GET_FORMS, forms: res }); }.bind(this)) }
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
. Usingredux-thunk
, you simply return a function that acceptsdispatch
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.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 callingdispatch
. You're 50% of the way there though, in that you passed the action creators toconnect
, but even though you did that, you didn't call the bound action creators thatconnect
passes into prop--you called the unbound ones.So instead of calling
FormActions.getForms()
, you need to callthis.props.actions.formActions()
. The former is not bound, but the latter is. Calling the latter is the same as doingthis.props.dispatch(FormActions.getForms())
.And finally: you don't need to define
mapDispatchToProps
in this case. You can pass objects to that parameter ofconnect
. 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