Can't figure out "Error: Actions must be plain objects. Use custom middleware for async actions." - react-redux

I'm using React and Redux to build an app to request and subsequently display movie information from an API. In the console, I can get the requested data back but somewhere beyond that I hit the error - "Error: Actions must be plain objects. Use custom middleware for async actions."
Here's my code so far..
Search component:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { fetchMovie } from '../../actions/index';
class SearchBar extends Component {
constructor(props) {
super(props);
this.state = { term: '' };
this.onInputChange = this.onInputChange.bind(this);
this.onFormSubmit = this.onFormSubmit.bind(this);
}
onInputChange(e) {
this.setState({ term: e.target.value });
}
onFormSubmit(event) {
event.preventDefault();
this.props.fetchMovie(this.state.term);
this.setState({ term: '' });
}
render() {
return (
<div>
<form onSubmit={this.onFormSubmit} className="input-group">
<input
placeholder="Search by movie title, actor, or genre"
className="form-control"
value={this.state.term}
onChange={this.onInputChange}
/>
<span className="input-group-btn">
<button type="submit" className="btn btn-secondary">
Submit
</button>
</span>
</form>
<br />
</div>
);
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ fetchMovie }, dispatch);
}
export default connect(null, mapDispatchToProps)(SearchBar);
Action...
import axios from 'axios';
const API_KEY = '449a384f';
export const FETCH_MOVIE = 'FETCH_MOVIE';
let movies = [];
export function fetchMovie(term) {
const request = axios
.get(`http://www.omdbapi.com/?s=${term}&apikey=${API_KEY}`)
.then(response => {
movies = response.data;
response.json = movies;
})
.catch(error => console.log(error));
return {
type: FETCH_MOVIE,
payload: request,
};
}
Reducer...
import { FETCH_MOVIE } from '../actions/index';
export default function(state = null, action) {
switch (action.type) {
case FETCH_MOVIE:
return [action.payload.data, ...state];
}
return state;
}
CombineReducer...
import { combineReducers } from 'redux';
import MovieReducer from './movie_reducer';
const rootReducer = combineReducers({
movie: MovieReducer,
});
export default rootReducer;
Store...
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
// import ReduxThunk from 'redux-thunk';
import { BrowserRouter, Switch, Route } from 'react-router-dom';
import { createStore, applyMiddleware } from 'redux';
import ReduxPromise from 'redux-promise';
import './index.css';
import App from './App';
import Login from './components/login/login';
import reducers from './reducers/reducer';
import '../node_modules/bootstrap/dist/css/bootstrap.min.css';
import { findDOMNode } from 'react-dom';
import $ from 'jquery';
const createStoreWithMiddleware = applyMiddleware(ReduxPromise)(createStore);
ReactDOM.render(
<Provider store={createStoreWithMiddleware(reducers)}>
<BrowserRouter>
<Switch>
<Route path="/Login" component={Login} />
<Route path="/" component={App} />
</Switch>
</BrowserRouter>
</Provider>,
document.querySelector('#root')
);
Thanks for the help!

Vanilla Redux requires that you return a plain JavaScript object in your action creators. Whenever, you need to perform async operations, you need to introduce middleware like redux-thunk or redux-promise to intercept the returned object an perform additional work so that a plain JavaScript object can ultimately be returned.
You're attempting to use redux-promise, but what you're returning is not causing the middleware to be invoked. Your fetchMovie() method is returning a plain object containing a Promise. In order to use redux-promise, the method needs to return a Promise.
From their documentation:
createAction('FETCH_THING', async id => {
const result = await somePromise;
return result.someValue;
});

Probably, the reason is that you're trying to return promise in the action to reducer.
You're using thunk, so you always can dispatch from action creator
export const fetchMovie = term => dispatch => axios
.get(`http://www.omdbapi.com/?s=${term}&apikey=${API_KEY}`)
.then(response => {
dispatch({
type: FETCH_MOVIE,
payload: response,
});
})
.catch(error => console.log(error));

Related

My state in store not getting updated , rather state attribute gets disappeared once action fires as checked in Redux dev tool

I have created an input field and am dispatching an action in the onChange method of the input field. i have a state attribute text:'' which should get updated once user starts typing in the input field but same is not happening , instead text attribute in the state disappears once action is fired as checked in Redux Dev tool.
Also have below 2 queries - read the docs , but still not clear
Why do we have to pass initialState in createStore as I have already passed in state to reducers , though have passed empty initialState to createStore .
Do I need to use mapStateToProps in my case as I am not making use of any state in my component
Action file - searchActions.js
import { SEARCH_MOVIE } from "./types";
export const searchMovie = text => ({
type: SEARCH_MOVIE,
payload: text
});
Reducer file - searchReducer.js
import { SEARCH_MOVIE } from "../actions/types";
const initialState = {
text: ""
};
const searchReducer = (state = initialState, action) => {
console.log(action);
switch (action.type) {
case SEARCH_MOVIE:
return {
...state,
text: action.payload
};
default:
return state;
}
};
export default searchReducer;
Root reducer file - index.js
import { combineReducers } from "redux";
import searchReducer from "./searchReducer";
const rootReducer = combineReducers({
searchReducer
});
export default rootReducer;
Store file - store.js
import { createStore } from "redux";
import rootReducer from "./reducers";
const initialState = {};
const store = createStore(
rootReducer,
initialState,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
export default store;
Form containing the input field - SearchForm.js
import React, { Component } from "react";
import { searchMovie } from "../../actions/searchActions";
import { connect } from "react-redux";
export class SearchForm extends Component {
onChange = e => {
this.props.searchMovie(e.target.value);
};
render() {
return (
<div className="jumbotron jumbotron-fluid mt-5 text-center">
<form className="form-group">
<input
type="text"
className="form-control"
placeholder="Search Movies"
onChange={e => this.onChange(e)}
></input>
<button type="submit" className="btn btn-primary mt-3 btn-bg">
Search
</button>
</form>
</div>
);
}
}
const mapStateToProps = state => {
return {
text: state.text
};
};
const mapDispatchToProps = dispatch => ({
searchMovie: () => dispatch(searchMovie())
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(SearchForm);
Entry file - App.js
import React, { Component } from "react";
import { Provider } from "react-redux";
import SearchForm from "./SearchForm";
import store from "./store";
import "./App.css";
class App extends Component {
render() {
return (
<Provider store={store}>
<SearchForm />
</Provider>
);
}
}
export default App;
You call this function with a parameter
this.props.searchMovie(e.target.value);
However in here you do not provide any parameter
const mapDispatchToProps = dispatch => ({
searchMovie: () => dispatch(searchMovie())
});
should be
const mapDispatchToProps = dispatch => ({
searchMovie: movie => dispatch(searchMovie(movie))
});
For your questions
1. Why do we have to pass initialState in createStore as I have already passed in state to reducers , though have passed empty initialState to createStore .
You haven't passed initialState to reducer, in fact you can't. This is different const searchReducer = (state = initialState, action) => {. It's a javascript syntax which acts as a default value for a parameter if you don't provide one.
2. Do I need to use mapStateToProps in my case as I am not making use of any state in my component
Yes you don't have to. You can set is as undefined

Redux state changed but component props is not updating

I am using redux to control an Ant Design Modal component with a boolean state. Basically it has a button that dispatch action to change the state, and the component will read the state value.
The state is changed properly but the component props value is not updating accordingly. Not sure why it is not working.
I have tried different approaches in reducer like creating a new boolean object to avoid mutating the state but no luck.
myAction.js
export const modalVisibilityOn = () => ({
type: 'MODAL_ON'
})
export const modalVisibilityOff = () => ({
type: 'MODAL_OFF'
})
myReducer.js
const modalVisibility = (state = false, action) => {
switch (action.type){
case 'MODAL_ON':
return true
case 'MODAL_OFF':
return false
default:
return state
}
}
export default modalVisibility
myRootReducer.js
import { combineReducers } from 'redux'
import modalVisibility from './signPage/myReducer'
export default combineReducers({
modalVisibility
})
myModal.js
import React from "react";
import PropTypes from 'prop-types'
import { Modal, Input } from 'antd';
import { connect } from 'react-redux'
import { modalVisibilityOff } from '../../reducers/signPage/myAction'
class myModal extends React.Component {
render() {
const { visibility, handleOk, handleCancel } = this.props;
myModal.propTypes = {
visibility: PropTypes.bool.isRequired,
handleOk: PropTypes.func.isRequired,
handleCancel: PropTypes.func.isRequired,
}
return (
<Modal
title="Sign"
visible={visibility}
onOk={handleOk}
onCancel={handleCancel}
closable={false}
>
<p>Please input your staff ID</p>
<Input addonBefore="Staff ID" />
</Modal>
);
}
}
const mapStateToProps = state => {
return {
visibility: state.modalVisibility
}
}
const mapDispatchToProps = dispatch => ({
handleOk: () => dispatch(modalVisibilityOff()),
handleCancel: () => dispatch(modalVisibilityOff()),
})
export default connect(
mapStateToProps,mapDispatchToProps
)(myModal)
myModalContainer.js
import React from "react";
import { Input } from "antd";
import { Button } from 'antd';
import { Row, Col } from 'antd';
import { Typography } from 'antd';
import PropTypes from 'prop-types'
import myModal from '../../dialogs/signPage/myModal';
import { connect } from 'react-redux'
import { modalVisibilityOn } from '../../reducers/signPage/myAction'
class myModalContainer extends React.Component {
render() {
const { Title } = Typography;
const { onClick } = this.props;
myModalContainer.propTypes = {
onClick: PropTypes.func.isRequired
}
return (
<div className="search-container-parent">
<Row className="search-container">
<Col className="search-col1" xs={24} sm={12}>
<Input size="large" style={{width:'40%'}} id="issueReturnNo" placeholder="QR code here"/>
<Button size="large">SEARCH</Button>
<div className="signBtn-div">
<Button size="large" type="primary" onClick={onClick} >SIGN</Button>
<myModal />
</div>
</Col>
<Col xs={24} sm={12}>
<Title className="issueLog-title" level={3} style={{color:"#F08080"}}>Issue</Title>
</Col>
</Row>
</div>
);
}
}
const mapDispatchToProps = dispatch => ({
onClick: () => dispatch(modalVisibilityOn())
})
export default connect(
null, mapDispatchToProps
)(myModalContainer);
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import rootReducer from './myRootReducer'
const store = createStore(rootReducer,window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__())
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root'));
serviceWorker.unregister();
I expect the visibility props on myModal.js would be true when the sign button on myModalContainer.js is clicked, but the it keep showing false.
Any help would be appreciated. Thanks!
After lots of researches and trials, It turns out that my code has no problem..
The reason why it is not working as expected, is due to the redux and react-redux version. After switching package.json dependencies versions back to the one that redux official tutorial are using, the application is running without any problem.
In case anyone have the same problem, here is the version I am using now for my app in npm:
redux: ^3.5.2
react-redux: ^5.0.7
Update:
Just found out that the cause of the problem comes from conflicts between older version modules and newer version modules.
Therefore by updating all the dependencies in package.json to the latest version, the app can also run smoothly. It is not necessary to downgrade react-redux and redux.

Redux store error: <Provider> does not support changing `store` on the fly

I am trying to setup my first react/redux/rails app. I am using react_on_rails gem to pass in my current_user and gyms props.
Everything appears to work ok so far except my console shows error:
<Provider> does not support changing `store` on the fly. It is most likely that you see this error because you updated to Redux 2.x and React Redux 2.x which no longer hot reload reducers automatically. See https://github.com/reactjs/react-redux/releases/tag/v2.0.0 for the migration instructions.
Googling gives me hints that this can happen if you try to create a store within a render method, which causes store to get recreated. I don't see that issue here. Where am I going wrong?
//App.js
import React from 'react';
import { Provider } from 'react-redux';
import configureStore from '../store/gymStore';
import Gym from '../components/Gym';
const App = props => (
<Provider store={configureStore(props)}>
<Gym />
</Provider>
);
export default App;
../store/gymStore.jsx
//the store creation.
/*
// my original way
import { createStore } from 'redux';
import gymReducer from '../reducers/';
const configureStore = railsProps => createStore(gymReducer, railsProps);
export default configureStore;
*/
/* possible fix: https://github.com/reactjs/react-redux/releases/tag/v2.0.0 */
/* but adding below does not resolve error */
import { createStore } from 'redux';
import rootReducer from '../reducers/index';
export default function configureStore(railsProps) {
const store = createStore(rootReducer, railsProps);
if (module.hot) {
// Enable Webpack hot module replacement for reducers
module.hot.accept(() => {
const nextRootReducer = require('../reducers').default;
store.replaceReducer(nextRootReducer);
});
}
return store;
}
I am not sure my rendered component is necessary but in case it is:
//compenents/Gym.jsx
import PropTypes from 'prop-types';
import React from 'react';
import { connect } from 'react-redux';
import LeftMenu from './LeftMenu';
class Gym extends React.Component {
static propTypes = {
//name: PropTypes.string.isRequired // this is passed from the Rails view
};
/**
* #param props - Comes from your rails view.
*/
constructor(props) {
super(props);
this.state = {
current_user: this.props.current_user,
gyms: JSON.parse(this.props.gyms),
active_gym: 1, //JSON.parse(this.props.gyms)[0],
name: 'sean',
title: 'Gym Overview'
};
}
updateName = name => {
this.setState({ name });
};
isLoggedIn = () => {
if (this.state.current_user.id != '0') {
return <span className="text-success"> Logged In!</span>;
} else {
return <span className="text-danger"> Must Log In</span>;
}
};
isActive = id => {
if (this.state.active_gym == id) {
return 'text-success';
}
};
render() {
return (
<div className="content">
<h2 className="content-header">{this.state.title}</h2>
{LeftMenu()}
{this.state.current_user.id != '0' ? <span>Welcome </span> : ''}
{this.state.current_user.first_name}
<h3 className="content-header">Your Gyms</h3>
<ul>
{this.state.gyms.map((gym, key) => (
<li key={key} className={this.isActive(gym.id)}>
{gym.name}
</li>
))}
</ul>
{this.isLoggedIn()}
<hr />
{/*
<form>
<label htmlFor="name">Say hello to:</label>
<input
id="name"
type="text"
value={this.state.name}
onChange={e => this.updateName(e.target.value)}
/>
</form>
*/}
</div>
);
}
}
function mapStateToProps(state) {
return {
current_user: state.current_user,
gyms: state.gyms,
active_gym: state.active_gym
};
}
export default connect(mapStateToProps)(Gym);

React Redux mapStateToProps not firing on update

I have a connected component that maintains a display "state" along with a few other things that are needed for communication between a couple of components. I have two connected components that are children of this over-arching component. Depending on a flag that is within the "state" component one or the other child components will render. It might be better to just show the code:
EditorState Component:
import React from 'react';
import {connect} from 'react-redux';
import Library from '../library/index';
import Editor from '../editor/index';
import {
initialize,
editorState
} from './actions';
class EditorState extends React.Component {
componentWillMount() {
const {dispatch} = this.props;
dispatch(initialize());
}
render() {
const {state} = this.props;
switch(state) {
case editorState.Library:
return <Library />
case editorState.Editor:
return <Editor />
default:
return null;
}
}
}
export default connect(state => {
return state.EditorStateReducer;
})(EditorState);
EditorState Actions:
export const EDITOR_STATE_INITIALIZE = 'EDITOR_STATE_INITIALIZE';
export const editorState = {
Library: 'library',
Editor: 'editor'
}
export const initialize = ({
type: EDITOR_STATE_INITIALIZE,
state: editorState.Library
});
EditorState Reducer:
import {
EDITOR_STATE_INITIALIZE
} from './actions';
const init = () => ({
state: null
});
export default (state = init(), action) => {
switch(action.type) {
case EDITOR_STATE_INITIALIZE:
return {
...state,
state: action.state
}
default:
return {...state}
}
}
Library Component:
import React from 'react';
import {connect} from 'react-redux';
import {Page} from '../../../components/page/index';
import LibraryItem from '../../../components/library-item/library-item';
import {
initialize
} from './actions';
class Library extends React.Component {
componentWillMount() {
const {dispatch} = this.props;
dispatch(initialize());
}
render() {
const {templates} = this.props;
const editorTemplates = templates.map(template =>
<LibraryItem template={template} />
);
return (
<Page>
<div className="card-flex library-table">
{editorTemplates}
</div>
</Page>
)
}
}
export default connect(state => {
return state.LibraryReducer;
})(Library);
Library Actions:
import {
client,
serviceUrl
} from '../../../common/client';
export const LIBRARY_INITIALIZE = 'LIBRARY_INITIALIZE';
export const initialize = () => dispatch => {
client.get(`${serviceUrl}/templates`).then(resp => {
dispatch({
type: LIBRARY_INITIALIZE,
templates: resp.templates
});
});
}
Library Reducer:
import {
LIBRARY_INITIALIZE
} from './actions';
const init = () => ({
templates: []
});
export default (state = init(), action) => {
switch(action.type) {
case LIBRARY_INITIALIZE:
return {
...state,
templates: action.templates
}
default:
return {...state}
}
}
The problem that I am having is that the mapStateToProps in the Library Component is not being called upon the dispatch of LIBRARY_INITIALIZE. I have breakpoints in both mapStateToProps in the EditorState and Library, and a breakpoint in the LIBRARY_INITIALIZE switch in the Library reducer. Debugging page load goes like this:
EditorState mapStateToProps - state.EditorStateReducer.state is null
EditorState mapStateToProps - state.EditorStateReducer.state == editorState.Library
Library mapStateToProps - state.LibraryReducer.templates == []
Library Reducer Initialize - action.templates == [{template1}, {template2}, etc]
EditorState mapStateToProps - state.LibraryReducer.templates == [{template1}, {template2}, etc]
Then nothing. I would expect the Library mapStateToProps to fire as well after this so that the Library can re-render with the templates. However, this is not happening. Why is this not happening? I am ready to pull my hair out over this one...
You cannot be 100% sure that the updated state is rendered right after the dispatch call. mapStatetoProps is called when the component is about to re-render, which depends on whether React batches the updates or not. By default, React batches updates from event handlers.
You can refer https://github.com/reactjs/react-redux/issues/291

componentDidUpdate does not fire

So I've been struggling to figure out the react-redux ecosystem for a while now. I'm almost there but there is still something that keep giving is me issues, and that's the componentDidUpdate method. When I dispatch an async action, the store is reducer is called correctly and the component's state does update.
But for some reason, the componentDidUpdate method does not fire, there is no re-render, and I cannot access the updated props. I can see it change in devtools, if I console.log(this.props.blogStore). At first it shows as an empty object but when on click it opens and shows the updated state.
I've tried as many life cycle methods as I can but nothing seems to work, including componentWillReceiveProps.
Any idea what I'm doing wrong?
Here is the code:
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './App';
import Datastore from 'Datastore';
const store = Datastore()
store.subscribe(() => console.log("state changed", store.getState()))
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('app')
);
Datastore.js
import { combineReducers, createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk'
import Mainmenu from 'reducers/Mainmenu';
import Blogstore from 'reducers/Blogstore';
const reducer = combineReducers({
Mainmenu,
Blogstore,
})
export default function Datastore() {
const store = createStore(
reducer,
applyMiddleware(thunk)
)
return store
}
reducer
import Article from 'lib/Article';
import { ARTICLE_LOAD, ARTICLE_UPDATE, SAVE_ARTICLE_LIST } from 'actionTypes';
const initialBlogState = {
}
const Blogstore = (state=initialBlogState, action) => {
switch(action.type) {
case SAVE_ARTICLE_LIST:
state.init = true
state.articles = action.payload
return state
case ARTICLE_LOAD:
return state
case ARTICLE_UPDATE:
return state
}
return state
}
export default Blogstore;
blog-actions.js
import { ARTICLE_LOAD, ARTICLE_UPDATE, SAVE_ARTICLE_LIST } from 'actionTypes';
import APIFetch from '../lib/Fetch';
export function getArticlePids() {
return dispatch => {
APIFetch().get("/blog/list").then(response => {
dispatch({
type: SAVE_ARTICLE_LIST,
payload: response.data
})
})
}
}
component
import React from 'react';
import { connect } from 'react-redux';
import * as blogActions from '../actions/blog-actions';
#connect(state => ({
blogStore: state.Blogstore
}))
export default class Blog extends React.Component {
constructor() {
super()
}
componentDidMount() {
this.props.dispatch(blogActions.getArticlePids())
}
componentDidUpdate(prevProps) {
console.log("update", prevProps)
}
render() {
console.log("render", this.props.blogStore)
return (
<div><h1>Blog</h1></div>
)
}
}
That is pretty much it. I won't bother pasting the App and Router that are between index.js and the component because there is nothing of interest there. Just a basic react router and components that have nothing to do with this.
You need to return a new object from your reducer, like this:
import Article from 'lib/Article';
import { ARTICLE_LOAD, ARTICLE_UPDATE, SAVE_ARTICLE_LIST } from 'actionTypes';
const initialBlogState = {
}
const Blogstore = (state=initialBlogState, action) => {
switch(action.type) {
case SAVE_ARTICLE_LIST:
return Object.assign({}, state, {
init: true,
articles: action.payload,
})
case ARTICLE_LOAD:
return state
case ARTICLE_UPDATE:
return state
}
return state
}
export default Blogstore;
Otherwise, if you try to update your state directly (as you are doing currently) it will only mutate the internal reference of the state and react components won't be able to detect the change and wont re-render. Read more here.

Resources