如何修复 'Actions must be plain objects. Use custom middleware for async actions.'
How to fix 'Actions must be plain objects. Use custom middleware for async actions.'
我正在制作一个 React-redux 组件以嵌入到 Laravel blade 文件中。在反应方面,
我正在使用带有 thunk 的 redux,当我尝试从 Laravel 路由获取没有 thunk 的数据时,它会正常运行。
但是当我在 action creator 中使用 axios 请求异步获取数据时。它给出了:
'Uncaught Error: Actions must be plain objects. Use custom middleware for async actions.'
这是react端的入口组件
Entry.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import reducer from '../store/reducers/reducer';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import thunk from "redux-thunk";
const store = createStore(reducer, applyMiddleware(thunk)
+window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
// console.log(getState());
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>
, document.getElementById('like_post'));
这是App.js的主要成分
import React, { Component } from "react";
import { connect } from "react-redux";
import PropTypes from 'prop-types';
import axios from 'axios';
import {getCurrentPostLikes} from '../store/actions/actions';
class App extends Component {
constructor(props) {
super(props)
var domain = window.location.hostname;
var url = window.location.pathname;
var urlsplit = url.split("/");
var post_slug = urlsplit[urlsplit.length - 1];
this.state={
post_slug: post_slug,
post_likes:''
}
}
kFormatter(num) {
return num > 999 ? (num / 1000).toFixed(1) + 'k' : num
}
componentDidMount() {
this.props.getCurrentPostLikes();
// axios.get(`/api/get_post_likes/${this.state.post_slug}`)
// .then(response => {
// console.log(response.data.likes);
// this.setState({
// post_likes: response.data.likes
// })
// })
}
render() {
return (
<div className="App">
<a href="javascript:"><img src="/images/love.svg" alt="post like" width="50px" height="50px"/></a>
<p>{this.kFormatter(this.state.post_likes)}</p>
<p><span>{this.props.likes}</span></p>
</div>
);
}
}
export default connect(null, {getCurrentPostLikes})(App);
// export default connect( mapStateToProps, mapDispachToProps )(App);
这是 actions.js 文件 /store/actions/actions.js
// types.js is also defined properly as
// export const GET_POST_LIKES = 'GET_POST_LIKES';
import axios from 'axios';
import {GET_POST_LIKES} from './types';
// Get current post likes
export const getCurrentPostLikes = () => dispatch => {
return dispatch => {
setTimeout(() => {
axios.get(`/api/get_post_likes/2`)
.then(res => {
// console.log(response.data.likes);
// console.log(getState());
setTimeout(() => {
dispatch({
type: GET_POST_LIKES,
payload: res.data.likes
})
}, 4000);
})
.catch(err => {
dispatch({
type: GET_POST_LIKES,
payload: {}
})
})
}, 3000);
}
}
也试过这个 action creator,但还是一样的错误
export const getCurrentPostLikes = () => {
return dispatch => {
axios.get(`/api/get_post_likes/2`)
.then(res => {
dispatch({
type: GET_POST_LIKES,
payload: res.data.likes
})
})
.catch(err => {
dispatch({
type: GET_POST_LIKES,
payload: {}
})
})
}
}
这是/store/reducers/reducer.js
下的reducers.js文件
import { GET_POST_LIKES } from '../actions/types';
const initialState = {
likes: null
};
const reducer = (state=initialState, action) => {
const newState = {...state};
switch(action.type){
case 'GET_POST_LIKES':
return {
...state,
post: action.payload
}
case 'LIKE_UP':
newState.likes += action.value
break;
}
return newState;
};
export default reducer;
现在,这应该 return 帖子 table 的字段值,其中 post id = 2
.
看来你的动作多了一个dispatch =>
export const getCurrentPostLikes = () => dispatch => {
setTimeout(() => {
axios.get(`/api/get_post_likes/2`)
.then(res => {
// console.log(response.data.likes);
// console.log(getState());
setTimeout(() => {
dispatch({
type: GET_POST_LIKES,
payload: res.data.likes
})
}, 4000);
})
.catch(err => {
dispatch({
type: GET_POST_LIKES,
payload: {}
})
})
}, 3000);
}
这是使用 redux-thunk 的 action creator 柯里化函数的 'signature':
export const getCurrentPostLikes = () => async dispatch => {
const response = await axios.get(`/api/get_post_likes/2`);
dispatch({ type: GET_POST_LIKES, payload: response.data.likes });
};
您的问题在 Entry.js,这一行:
const store = createStore(reducer, applyMiddleware(thunk)
+window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
您正在将 thunk 中间件设置为第二个参数。第二个参数是初始状态。
thunk中间件应该是createStore函数第三个参数composed enhancer的一部分。
应按如下方式应用参数:
createStore(
connectRouter(history)(rootReducer),
initialState,
composedEnhancers
)
完整示例:
import { createStore, applyMiddleware, compose } from 'redux'
import { connectRouter, routerMiddleware } from 'connected-react-router'
import thunk from 'redux-thunk'
import createHistory from 'history/createHashHistory'
import rootReducer from './core/reducers'
export const history = createHistory()
const initialState = {}
const enhancers = []
const middleware = [thunk, routerMiddleware(history)]
if (process.env.NODE_ENV === 'development') {
const devToolsExtension = window.__REDUX_DEVTOOLS_EXTENSION__
if (typeof devToolsExtension === 'function') {
enhancers.push(devToolsExtension())
}
}
const composedEnhancers = compose(
applyMiddleware(...middleware),
...enhancers
)
export default createStore(
connectRouter(history)(rootReducer),
initialState,
composedEnhancers
)
这就是您希望动作创建器的外观:
export const getCurrentPosts = () => {
return async function(dispatch, getState) {
const response = await axios.get("/api/get_post_likes");
dispatch({ type: "GET_POST_LIKES", payload: response });
};
};
将 Promises 排除在外并使用 ES7 async/await
语法并且也将 setTimeout
排除在外,除非您可以提供一个非常好的理由让异步请求超时已经花费了非零时间来完成,也许我不明白,但你确实想要这些数据,不是吗?您的减速器有必要完成它们的工作并最终更新您的应用程序中的状态。
我上面的内容并不完美,因为它的样板文件,就像你真的不需要 getState
一样,不是真的,是的,它是中间件的一部分,但如果你不打算使用某些东西,无需对其进行定义,但无论如何,我正试图让您达到这样一个程度,即您的动作创作者再次工作并且您正在保护您的眼睛和思想。
我正在制作一个 React-redux 组件以嵌入到 Laravel blade 文件中。在反应方面,
我正在使用带有 thunk 的 redux,当我尝试从 Laravel 路由获取没有 thunk 的数据时,它会正常运行。
但是当我在 action creator 中使用 axios 请求异步获取数据时。它给出了:
'Uncaught Error: Actions must be plain objects. Use custom middleware for async actions.'
这是react端的入口组件
Entry.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import reducer from '../store/reducers/reducer';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import thunk from "redux-thunk";
const store = createStore(reducer, applyMiddleware(thunk)
+window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
// console.log(getState());
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>
, document.getElementById('like_post'));
这是App.js的主要成分
import React, { Component } from "react";
import { connect } from "react-redux";
import PropTypes from 'prop-types';
import axios from 'axios';
import {getCurrentPostLikes} from '../store/actions/actions';
class App extends Component {
constructor(props) {
super(props)
var domain = window.location.hostname;
var url = window.location.pathname;
var urlsplit = url.split("/");
var post_slug = urlsplit[urlsplit.length - 1];
this.state={
post_slug: post_slug,
post_likes:''
}
}
kFormatter(num) {
return num > 999 ? (num / 1000).toFixed(1) + 'k' : num
}
componentDidMount() {
this.props.getCurrentPostLikes();
// axios.get(`/api/get_post_likes/${this.state.post_slug}`)
// .then(response => {
// console.log(response.data.likes);
// this.setState({
// post_likes: response.data.likes
// })
// })
}
render() {
return (
<div className="App">
<a href="javascript:"><img src="/images/love.svg" alt="post like" width="50px" height="50px"/></a>
<p>{this.kFormatter(this.state.post_likes)}</p>
<p><span>{this.props.likes}</span></p>
</div>
);
}
}
export default connect(null, {getCurrentPostLikes})(App);
// export default connect( mapStateToProps, mapDispachToProps )(App);
这是 actions.js 文件 /store/actions/actions.js
// types.js is also defined properly as
// export const GET_POST_LIKES = 'GET_POST_LIKES';
import axios from 'axios';
import {GET_POST_LIKES} from './types';
// Get current post likes
export const getCurrentPostLikes = () => dispatch => {
return dispatch => {
setTimeout(() => {
axios.get(`/api/get_post_likes/2`)
.then(res => {
// console.log(response.data.likes);
// console.log(getState());
setTimeout(() => {
dispatch({
type: GET_POST_LIKES,
payload: res.data.likes
})
}, 4000);
})
.catch(err => {
dispatch({
type: GET_POST_LIKES,
payload: {}
})
})
}, 3000);
}
}
也试过这个 action creator,但还是一样的错误
export const getCurrentPostLikes = () => {
return dispatch => {
axios.get(`/api/get_post_likes/2`)
.then(res => {
dispatch({
type: GET_POST_LIKES,
payload: res.data.likes
})
})
.catch(err => {
dispatch({
type: GET_POST_LIKES,
payload: {}
})
})
}
}
这是/store/reducers/reducer.js
下的reducers.js文件import { GET_POST_LIKES } from '../actions/types';
const initialState = {
likes: null
};
const reducer = (state=initialState, action) => {
const newState = {...state};
switch(action.type){
case 'GET_POST_LIKES':
return {
...state,
post: action.payload
}
case 'LIKE_UP':
newState.likes += action.value
break;
}
return newState;
};
export default reducer;
现在,这应该 return 帖子 table 的字段值,其中 post id = 2
.
看来你的动作多了一个dispatch =>
export const getCurrentPostLikes = () => dispatch => {
setTimeout(() => {
axios.get(`/api/get_post_likes/2`)
.then(res => {
// console.log(response.data.likes);
// console.log(getState());
setTimeout(() => {
dispatch({
type: GET_POST_LIKES,
payload: res.data.likes
})
}, 4000);
})
.catch(err => {
dispatch({
type: GET_POST_LIKES,
payload: {}
})
})
}, 3000);
}
这是使用 redux-thunk 的 action creator 柯里化函数的 'signature':
export const getCurrentPostLikes = () => async dispatch => {
const response = await axios.get(`/api/get_post_likes/2`);
dispatch({ type: GET_POST_LIKES, payload: response.data.likes });
};
您的问题在 Entry.js,这一行:
const store = createStore(reducer, applyMiddleware(thunk)
+window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
您正在将 thunk 中间件设置为第二个参数。第二个参数是初始状态。 thunk中间件应该是createStore函数第三个参数composed enhancer的一部分。
应按如下方式应用参数:
createStore(
connectRouter(history)(rootReducer),
initialState,
composedEnhancers
)
完整示例:
import { createStore, applyMiddleware, compose } from 'redux'
import { connectRouter, routerMiddleware } from 'connected-react-router'
import thunk from 'redux-thunk'
import createHistory from 'history/createHashHistory'
import rootReducer from './core/reducers'
export const history = createHistory()
const initialState = {}
const enhancers = []
const middleware = [thunk, routerMiddleware(history)]
if (process.env.NODE_ENV === 'development') {
const devToolsExtension = window.__REDUX_DEVTOOLS_EXTENSION__
if (typeof devToolsExtension === 'function') {
enhancers.push(devToolsExtension())
}
}
const composedEnhancers = compose(
applyMiddleware(...middleware),
...enhancers
)
export default createStore(
connectRouter(history)(rootReducer),
initialState,
composedEnhancers
)
这就是您希望动作创建器的外观:
export const getCurrentPosts = () => {
return async function(dispatch, getState) {
const response = await axios.get("/api/get_post_likes");
dispatch({ type: "GET_POST_LIKES", payload: response });
};
};
将 Promises 排除在外并使用 ES7 async/await
语法并且也将 setTimeout
排除在外,除非您可以提供一个非常好的理由让异步请求超时已经花费了非零时间来完成,也许我不明白,但你确实想要这些数据,不是吗?您的减速器有必要完成它们的工作并最终更新您的应用程序中的状态。
我上面的内容并不完美,因为它的样板文件,就像你真的不需要 getState
一样,不是真的,是的,它是中间件的一部分,但如果你不打算使用某些东西,无需对其进行定义,但无论如何,我正试图让您达到这样一个程度,即您的动作创作者再次工作并且您正在保护您的眼睛和思想。