如何修复错误 "Actions must be plain objects"?
How to fix an error "Actions must be plain objects"?
我刚接触sagas,我无法解决"Actions must be plain objects. Use custom middleware for async actions."
的问题
我附上了所有必要的代码。已经打破了他的头,解决了这个问题。
希望得到您的帮助。
我查看了 sagas 的文档,但没有找到有关此错误的任何信息。
我还看了反应样板,那里已经有 sagas,但我想在 CRA
上做这个
行动
import { AXIOS } from "../api";
import { takeLatest, put, call } from "redux-saga/effects";
export const GET_GENRES_PENDING = "GENRES::GET_GENRES_PENDING";
export const GET_GENRES_FULFILLED = "GENRES::GET_GENRES_FULFILLED";
export const GET_GENRES_REJECTED = "GENRES::GET_GENRES_REJECTED";
export const getGenresPending = () => ({
type: GET_GENRES_PENDING
});
export const getGenresFulfilled = data => ({
type: GET_GENRES_FULFILLED,
payload: data
});
export const getGenresRejected = error => ({
type: GET_GENRES_REJECTED,
payload: error
});
export function* getGenresAction() {
try {
yield put(getGenresPending());
const data = yield call(() => {
return AXIOS.get(
"/movie/list?api_key=5fcdb863130c33d2cb8f1612b76cbd30&language=ru-RU"
).then(response => {
console.log(response);
});
});
yield put(getGenresFulfilled(data));
} catch (error) {
yield put(getGenresRejected(error));
}
}
export default function* watchFetchGenres() {
yield takeLatest("FETCHED_GENRES", getGenresAction);
}
商店
import { applyMiddleware, compose, createStore } from "redux";
import createSagaMiddleware from "redux-saga";
import rootReducer from "./reducers";
import watchFetchGenres from "./actions/getGenresAction";
const sagaMiddleware = createSagaMiddleware();
export function configureStore(initialState) {
const middleware = [sagaMiddleware];
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
rootReducer,
initialState,
composeEnhancers(applyMiddleware(...middleware))
);
sagaMiddleware.run(watchFetchGenres);
return store;
}
index.js
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import App from "./containers/App";
import * as serviceWorker from "./serviceWorker";
import { configureStore } from "./core/configureStore.js";
const store = configureStore({});
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
serviceWorker.unregister();
App.js
import React from "react";
import { BrowserRouter as Router, Route } from "react-router-dom";
import MoviesContainer from "./MoviesContainer/MoviesContainer";
import FilterContainer from "./FilterContainer/FilterContainer";
import { Container, GlobalStyle } from "./style.js";
export default function App() {
return (
<Container className="app">
<GlobalStyle />
<Router>
<Route exact path="/" component={FilterContainer} />
<Route path="/movies" component={MoviesContainer} />
</Router>
</Container>
);
}
容器
import React, { useState, useEffect } from "react";
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import watchFetchGenres from "../../core/actions/getGenresAction";
import Card from "../../components/Card/Card";
import Button from "../../components/Button/Button";
import TextInput from "../../components/TextInput/TextInput";
import { TitleH1, TitleH2, TitleCard } from "../../components/Title/Title";
import { Container, SecondaryContainer } from "../style.js";
class FilterContainer extends React.Component {
// const dispatch = useDispatch();
// useEffect(() => {
// getGenresAction();
// // fetch('https://api.themoviedb.org/3/genre/movie/list?api_key=5fcdb863130c33d2cb8f1612b76cbd30&language=en-US')
// });
componentDidMount() {
this.props.watchFetchGenres();
}
render() {
return (
<Container>
<TitleH1 title="Фильтры" />
<SecondaryContainer>
<TextInput placeholder="Введите название фильма" />
</SecondaryContainer>
<SecondaryContainer filters>
<Card>
<TitleCard title="Фильтр по жанру" />
</Card>
<Card>
<TitleCard title="Фильтр по рейтингу" />
</Card>
<Card>
<TitleCard title="Фильтр по году" />
</Card>
</SecondaryContainer>
<SecondaryContainer>
<Button primary value="Применить фильтры" placeholder="lala" />
</SecondaryContainer>
</Container>
);
}
}
const mapStateToProps = state => ({
genres: state.genres
});
const mapDispatchToProps = dispatch =>
bindActionCreators({ watchFetchGenres }, dispatch);
export default connect(
mapStateToProps,
mapDispatchToProps
)(FilterContainer);
bindActionCreators({ watchFetchGenres }, dispatch);
watchFetchGenres 不是动作创作者,所以这是不正确的。动作创建者是 returns 动作的函数。您的代码中有 3 个示例:
export const getGenresPending = () => ({
type: GET_GENRES_PENDING
});
export const getGenresFulfilled = data => ({
type: GET_GENRES_FULFILLED,
payload: data
});
export const getGenresRejected = error => ({
type: GET_GENRES_REJECTED,
payload: error
});
这些是您应该绑定的类型。
您的 saga 正在侦听类型为 "FETCHED_GENRES" 的动作,因此 3 个现有的动作创建器将无法工作。您可能需要创建另一个动作创建器,如:
export const fetchGenres = () => ({
type: 'FETCHED_GENRES',
});
然后在您的 mapDispatchToProps 中,您将使用此动作创建器:
const mapDispatchToProps = dispatch =>
bindActionCreators({ fetchGenres }, dispatch);
并更新你调用它的地方:
componentDidMount() {
this.props.fetchGenres();
}
我刚接触sagas,我无法解决"Actions must be plain objects. Use custom middleware for async actions."
的问题我附上了所有必要的代码。已经打破了他的头,解决了这个问题。 希望得到您的帮助。
我查看了 sagas 的文档,但没有找到有关此错误的任何信息。 我还看了反应样板,那里已经有 sagas,但我想在 CRA
上做这个行动
import { AXIOS } from "../api";
import { takeLatest, put, call } from "redux-saga/effects";
export const GET_GENRES_PENDING = "GENRES::GET_GENRES_PENDING";
export const GET_GENRES_FULFILLED = "GENRES::GET_GENRES_FULFILLED";
export const GET_GENRES_REJECTED = "GENRES::GET_GENRES_REJECTED";
export const getGenresPending = () => ({
type: GET_GENRES_PENDING
});
export const getGenresFulfilled = data => ({
type: GET_GENRES_FULFILLED,
payload: data
});
export const getGenresRejected = error => ({
type: GET_GENRES_REJECTED,
payload: error
});
export function* getGenresAction() {
try {
yield put(getGenresPending());
const data = yield call(() => {
return AXIOS.get(
"/movie/list?api_key=5fcdb863130c33d2cb8f1612b76cbd30&language=ru-RU"
).then(response => {
console.log(response);
});
});
yield put(getGenresFulfilled(data));
} catch (error) {
yield put(getGenresRejected(error));
}
}
export default function* watchFetchGenres() {
yield takeLatest("FETCHED_GENRES", getGenresAction);
}
商店
import { applyMiddleware, compose, createStore } from "redux";
import createSagaMiddleware from "redux-saga";
import rootReducer from "./reducers";
import watchFetchGenres from "./actions/getGenresAction";
const sagaMiddleware = createSagaMiddleware();
export function configureStore(initialState) {
const middleware = [sagaMiddleware];
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
rootReducer,
initialState,
composeEnhancers(applyMiddleware(...middleware))
);
sagaMiddleware.run(watchFetchGenres);
return store;
}
index.js
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import App from "./containers/App";
import * as serviceWorker from "./serviceWorker";
import { configureStore } from "./core/configureStore.js";
const store = configureStore({});
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
serviceWorker.unregister();
App.js
import React from "react";
import { BrowserRouter as Router, Route } from "react-router-dom";
import MoviesContainer from "./MoviesContainer/MoviesContainer";
import FilterContainer from "./FilterContainer/FilterContainer";
import { Container, GlobalStyle } from "./style.js";
export default function App() {
return (
<Container className="app">
<GlobalStyle />
<Router>
<Route exact path="/" component={FilterContainer} />
<Route path="/movies" component={MoviesContainer} />
</Router>
</Container>
);
}
容器
import React, { useState, useEffect } from "react";
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import watchFetchGenres from "../../core/actions/getGenresAction";
import Card from "../../components/Card/Card";
import Button from "../../components/Button/Button";
import TextInput from "../../components/TextInput/TextInput";
import { TitleH1, TitleH2, TitleCard } from "../../components/Title/Title";
import { Container, SecondaryContainer } from "../style.js";
class FilterContainer extends React.Component {
// const dispatch = useDispatch();
// useEffect(() => {
// getGenresAction();
// // fetch('https://api.themoviedb.org/3/genre/movie/list?api_key=5fcdb863130c33d2cb8f1612b76cbd30&language=en-US')
// });
componentDidMount() {
this.props.watchFetchGenres();
}
render() {
return (
<Container>
<TitleH1 title="Фильтры" />
<SecondaryContainer>
<TextInput placeholder="Введите название фильма" />
</SecondaryContainer>
<SecondaryContainer filters>
<Card>
<TitleCard title="Фильтр по жанру" />
</Card>
<Card>
<TitleCard title="Фильтр по рейтингу" />
</Card>
<Card>
<TitleCard title="Фильтр по году" />
</Card>
</SecondaryContainer>
<SecondaryContainer>
<Button primary value="Применить фильтры" placeholder="lala" />
</SecondaryContainer>
</Container>
);
}
}
const mapStateToProps = state => ({
genres: state.genres
});
const mapDispatchToProps = dispatch =>
bindActionCreators({ watchFetchGenres }, dispatch);
export default connect(
mapStateToProps,
mapDispatchToProps
)(FilterContainer);
bindActionCreators({ watchFetchGenres }, dispatch);
watchFetchGenres 不是动作创作者,所以这是不正确的。动作创建者是 returns 动作的函数。您的代码中有 3 个示例:
export const getGenresPending = () => ({
type: GET_GENRES_PENDING
});
export const getGenresFulfilled = data => ({
type: GET_GENRES_FULFILLED,
payload: data
});
export const getGenresRejected = error => ({
type: GET_GENRES_REJECTED,
payload: error
});
这些是您应该绑定的类型。
您的 saga 正在侦听类型为 "FETCHED_GENRES" 的动作,因此 3 个现有的动作创建器将无法工作。您可能需要创建另一个动作创建器,如:
export const fetchGenres = () => ({
type: 'FETCHED_GENRES',
});
然后在您的 mapDispatchToProps 中,您将使用此动作创建器:
const mapDispatchToProps = dispatch =>
bindActionCreators({ fetchGenres }, dispatch);
并更新你调用它的地方:
componentDidMount() {
this.props.fetchGenres();
}