Sorry very common error, but in the test below don't understand how the offending action storeMgrAnnouncement(result) is not a plain object. The api call and the thunk action are mocked, but the offending action isn't.
home.test.js
/* not showing 3rd party imports */
import Home from '../../components/home';
import {getMgrAnnouncement} from "../../__mocks__/actions";
import {STORE_MGR_ANNOUNCEMENT} from "../../constants";
import {success} from "../../__fixtures__/announcementGet";
const mockStore = configureStore([thunk]);
describe('Home Page', () => {
var store = null;
const initialState = {};
beforeEach(() => {
store = mockStore(initialState);
shallow(<Home store={store} />);
});
it ('should store manager announcement after retrieving it', async () => {
await store.dispatch(getMgrAnnouncement());
expect(store.getActions()).toContainEqual({
type: STORE_MGR_ANNOUNCEMENT,
payload: success
});
});
__mocks__/actions/index.js
import { storeMgrAnnouncement } from '../../actions';
import { success } from '../../__fixtures__/announcementGet';
/* mock api call */
function announcementGet() {
return new Promise((resolve, reject) => {
process.nextTick(() => {
resolve(success)
})
})
}
/* mock thunk action */
export function getMgrAnnouncement() {
return function(dispatch, getState) {
return announcementGet()
.then(result => {
/*
ERROR: Actions must be plain objects. Use custom middleware for async actions.
*/
dispatch(storeMgrAnnouncement(result));
})
}
}
actions/index.js
import { STORE_MGR_ANNOUNCEMENT } from '../../constants';
export function storeMgrAnnouncement(result) {
return {
type: STORE_MGR_ANNOUNCEMENT,
payload: result
}
}
You are dispatching the result of getMgrAnnouncement()
store.dispatch(getMgrAnnouncement())
But that function returns a function:
function getMgrAnnouncement() {
return function(dispatch, getState) {
You must dispatch an object, not a function.
Related
I've been trying to dispatch a function that will call an async parse cloud function. It worked well in my other projects when i used them in functions. But this is the first time i'm using them in a component and when i call the dispatch from map dispatch to props, I get this error. Please help me out.
ProfileHeader.js
import React, { Component } from 'react';
import Cover_Image from './Cover_Image.jpg';
import Profile_Pic from './Profile_Pic.svg';
import './ProfileHeader.css';
import { connect } from 'react-redux';
import { fetchUserProfile } from '../../Redux/UserProfile-Redux/UserProfileActionMethods';
class ProfileHeader extends Component {
componentDidMount() {
this.props.fetchUserProfile()
}
render() {
return (
<div className="profile-header-layout"></div>
)
}
}
const mapStatetoProps = (state) => {
return {
profile: state.UserProfile
}
}
const mapDispatchtoProps = (dispatch) => {
return {
fetchUserProfile: () => { dispatch(fetchUserProfile()) }, dispatch,
}
}
export default connect(mapDispatchtoProps, mapStatetoProps)(ProfileHeader)
The action Method:
import Parse from 'parse/dist/parse.min.js';
import { FETCH_USERPROFILE_FAILURE, FETCH_USERPROFILE_REQUEST, FETCH_USERPROFILE_SUCCESS } from './UserProfileActions';
const params = { username: "prvnngrj" }
export const fetchUserProfileRequest = () => {
return {
type: FETCH_USERPROFILE_REQUEST
}
}
export const fetchUserProfileSuccess = (userprofiles) => {
return {
type: FETCH_USERPROFILE_SUCCESS,
payload: userprofiles
}
}
export const fetchUserProfileFailure = (error) => {
return {
type: FETCH_USERPROFILE_FAILURE,
payload: error
}
}
export const fetchUserProfile = () => {
return async dispatch => {
dispatch(fetchUserProfileRequest)
try {
const responsedata = await Parse.Cloud.run("GetUserProfileForUsername", params);
const userprofiles = responsedata;
dispatch(fetchUserProfileSuccess(userprofiles))
}
catch (error) {
const errorMessage = error.message
dispatch(fetchUserProfileFailure(errorMessage))
}
}
}
Please ignore parts of code which do not make it relevant, its straight from the project
You mixed up the order of your arguments, so this.props.dispatch is actually your state!
You need to change
export default connect(mapDispatchtoProps, mapStatetoProps)(ProfileHeader)
to:
export default connect(mapStatetoProps, mapDispatchtoProps)(ProfileHeader)
If you can switch to function components and the useSelector/useDispatch hooks you should. This is the current recommended approach and it's easier to use.
I am pretty sure i am returning an object and have used asyn and await on the promise within my action file. but this still keeps returing the error redux.js:205 Uncaught Error: Actions must be plain objects. Use custom middleware for async actions
https://codesandbox.io/s/frosty-nash-wdcjf?fontsize=14
my action file is returning an object
import axios from "axios";
export const LOAD_URL_STATUS = "LOAD_URL_STATUS";
export async function loadUrlStatus(url) {
const request = await axios
.get(url)
.then(response => {
console.log(response.status);
return response.status;
})
.catch(error => {
console.log("Looks like there was a problem: \n", error);
});
console.log(request);
console.log(LOAD_URL_STATUS);
return {
type: LOAD_URL_STATUS,
payload: request
};
}
it fails when calling this action in componenDidMount this.props.loadUrlStatus(url);
component
import React from 'react';
import TrafficLight from '../TrafficLight';
import {connect} from 'react-redux';
import {loadUrlStatus} from "../../actions";
//import {withPolling} from "../Polling";
//import Polling from "../Polling/polling";
import { bindActionCreators } from 'redux';
class TrafficLightContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
redOn: true,
yellowOn: false,
greenOn: false,
}
}
componentDidMount(){
console.log("componentDidMount")
const {pollingAction, duration, url} = this.props
//withPolling(this.props.loadUrlStatus(this.props.url),1)
/*
const {pollingAction, duration, url} = this.props
this.dataPolling = setInterval(
() => {
this.props.loadUrlStatus(url);
},
10000);
*/
this.props.loadUrlStatus(url);
};
componentWillUnmount() {
clearInterval(this.dataPolling);
}
render() {
console.log(this.props)
return (
<TrafficLight
Size={100}
onRedClick={() => this.setState({ redOn: !this.state.redOn })}
onGreenClick={() => this.setState({ greenOn: !this.state.greenOn })}
RedOn={this.state.redOn}
GreenOn={this.state.greenOn}
/>
)
}
}
const mapStateToProps = state => ({
...state
});
const mapDispatchToProps = dispatch => {
return bindActionCreators(
{
loadUrlStatus
},
dispatch
);
};
export default (
connect(mapStateToProps, mapDispatchToProps)(TrafficLightContainer));
index
import React from 'react';
import { render } from 'react-dom'
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { Provider } from 'react-redux';
import configureStore from './configureStore'
const store = configureStore();
const renderApp = () =>
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
if (process.env.NODE_ENV !== 'production' && module.hot) {
module.hot.accept('./App', renderApp)
}
renderApp();
serviceWorker.unregister();
The problem is that loadUrlStatus is async function, so it returns not object, but Promise, and object inside it promise.
To correct this, modify loadUrlStatus so it return another function. As you already applied thunk middleware during store creation, such function will be called inside redux. (You can see samples of async functions here)
export function loadUrlStatus(url) {
// Immediately return another function, which will accept dispatch as first argument. It will be called inside Redux by thunk middleware
return async function (dispatch) {
const request = await axios
.get(url)
.then(response => {
console.log(response.status);
return response.status;
})
.catch(error => {
console.log("Looks like there was a problem: \n", error);
});
console.log(request);
console.log(LOAD_URL_STATUS);
dispatch ({
type: LOAD_URL_STATUS,
payload: request
});
}
}
If you're using await in an action creator, you'll want to return a function from the action creator. Otherwise, return on object. A library like redux-thunk will help you do just that.
Your action creator would then look like this:
import axios from "axios";
export const LOAD_URL_STATUS = "LOAD_URL_STATUS";
export const loadUrlStatus(url) => async dispatch => {
try {
const response = await axios(url)
dispatch({
type: LOAD_URL_STATUS,
payload: response.status
})
} catch (error) {
// dispatch error
}
}
I'm using redux with redux-observable and get this strange error:
Actions must be plain objects. Use custom middleware for async >actions.
/* Component.jsx */
import React from "react"
import { serialNumberCheck } from '../actions'
const Component = props => {
...
<button
onClick={() => props.serialNumberCheck('123456789123456')}
>
Check
</button>
...
}
const mapDispatchToProps = dispatch =>
bindActionCreators({serialNumberCheck}, dispatch)
export default compose(
reduxForm({
...
}),
withStyles(styles),
connect(mapDispatchToProps)
)(Component)
/* actions.js */
export const SERIAL_NUMBER_CHECK = 'SERIAL_NUMBER_CHECK'
export const SERIAL_NUMBER_CHECK_SUCCESS = 'SERIAL_NUMBER_CHECK_SUCCESS'
export const serialNumberCheck = (serialNumber) => ({
type: SERIAL_NUMBER_CHECK,
payload: serialNumber
})
export const serialNumberCheckSuccess = (data) => ({
type: SERIAL_NUMBER_CHECK,
payload: data
})
/* epics.js */
...
import { serialNumberCheck } from "../actions"
import ... from 'rxjs'
...
function serialNumberCheckEpic(action$) {
return action$
.ofType(SERIAL_NUMBER_CHECK)
.switchMap((data) => {
return ajax.getJSON(`http://localhost:3004/sn/?sn=${data.payload}`)
.map((data) => data)
})
.map(data => {
if(data.length !== 0) {
serialNumberCheckSuccess({success: true});
}
})
}
...
export const rootEpic = combineEpics(
...
serialNumberCheckEpic
);
/* reducer.js */
import {
SERIAL_NUMBER_CHECK_SUCCESS,
} from '../actions'
...
export default function epicReducer(state = initialState, action) {
switch (action.type) {
case SERIAL_NUMBER_CHECK_SUCCESS:
return {
...state,
success: action.payload
}
}
}
/* JSON-SERVER RESPONSE */
[
{
"id": 1,
"sn": "123456789123456"
}
]
Inside component i'am calling function serialNumberCheck() and passing inside sierial number that we need to check.
Inside Epic im passing serial number to json-server that checks if this number exists in my "database". If serial number exists, server response is .json containing some parameters.
So if response isn't empty we need to write success: true inside redux store.
But in the end we get successfull GET request, and then error: Actions must be plain objects. Use custom middleware for async actions., but no changes inside redux-store and nothing from SERIAL_NUMBER_CHECK_SUCCESS action.
Finally, I found the solution. I've just missed the return before calling action inside my epic.
function serialNumberCheckEpic(action$) {
return action$
.ofType(SERIAL_NUMBER_CHECK)
.switchMap((data) => {
return ajax.getJSON(`http://localhost:3004/sn/?sn=${data.payload}`)
.map((data) => data)
})
.map(data => {
if(data.length !== 0) {
+ return serialNumberCheckSuccess({success: true});
}
})
}
I created a simple thunk action to get data from an API. It looks like this:
import fetch from 'isomorphic-fetch';
function json(response) {
return response.json();
}
/**
* Fetches booksfrom the server
*/
export function getBooks() {
return function(dispatch) {
return fetch("http://localhost:1357/book", {mode: "cors"})
.then(json)
.then(function(data) {
dispatch({
type: "GET_Books",
books: data
});
// This lets us use promises if we want
return(data);
});
}
};
Then, I wrote a test like this:
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import {getBooks} from '../../actions/getBooks';
import nock from 'nock';
import fetch from 'isomorphic-fetch';
import sinon from 'sinon';
it('returns the found devices', () => {
var devices = nock("http://localhost:1357")
.get("/book")
.reply(200,
{});
const store = mockStore({devices: []});
var spy = sinon.spy(fetch);
return store.dispatch(getBooks()).then(() => {
}).catch((err) => {
}).then(() => {
// https://gist.github.com/jish/e9bcd75e391a2b21206b
expect(spy.callCount).toEqual(1);
spy.retore();
});
});
This test fails - the call count is 0, not 1. Why isn't sinon mocking the function, and what do I need to do to make it mock the function?
You are importing fetch in your test file and not calling it anywhere. That is why call count is zero.
This begs the question of why you are testing that the action creator is called in the first place when the test description is "returns the found devices".
The main purpose of thunk action creators is to be an action creator which returns a function that can be called at a later time. This function that is called at a later time can receive the stores dispatch and state as its arguments. This allows the returned function to dispatch additional actions asynchronously.
When you are testing a thunk action creator you should be focus on whether or not the correct actions are dispatched in the following cases.
The request is made
The response is received and the fetch is successful
An error occurs and the fetch failed
Try something like the following:
export function fetchBooksRequest () {
return {
type: 'FETCH_BOOKS_REQUEST'
}
}
export function fetchBooksSuccess (books) {
return {
type: 'FETCH_BOOKS_SUCCESS',
books: books
}
}
export function fetchBooksFailure (err) {
return {
type: 'FETCH_BOOKS_FAILURE',
err
}
}
/**
* Fetches books from the server
*/
export function getBooks() {
return function(dispatch) {
dispatch(fetchBooksRequest(data));
return fetch("http://localhost:1357/book", {mode: "cors"})
.then(json)
.then(function(data) {
dispatch(fetchBooksSuccess(data));
// This lets us use promises if we want
return(data);
}).catch(function(err) {
dispatch(fetchBooksFailure(err));
})
}
};
Tests.js
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import fetchMock from 'fetch-mock' // You can use any http mocking library
import {getBooks} from '../../actions/getBooks';
const middlewares = [ thunk ]
const mockStore = configureMockStore(middlewares)
describe('Test thunk action creator', () => {
it('expected actions should be dispatched on successful request', () => {
const store = mockStore({})
const expectedActions = [
'FETCH_BOOKS_REQUEST',
'FETCH_BOOKS_SUCCESS'
]
// Mock the fetch() global to always return the same value for GET
// requests to all URLs.
fetchMock.get('*', { response: 200 })
return store.dispatch(fetchBooks())
.then(() => {
const actualActions = store.getActions().map(action => action.type)
expect(actualActions).toEqual(expectedActions)
})
fetchMock.restore()
})
it('expected actions should be dispatched on failed request', () => {
const store = mockStore({})
const expectedActions = [
'FETCH_BOOKS_REQUEST',
'FETCH_BOOKS_FAILURE'
]
// Mock the fetch() global to always return the same value for GET
// requests to all URLs.
fetchMock.get('*', { response: 404 })
return store.dispatch(fetchBooks())
.then(() => {
const actualActions = store.getActions().map(action => action.type)
expect(actualActions).toEqual(expectedActions)
})
fetchMock.restore()
})
})
My async action is not http, it doesn't use fetch api. How do i return a promise then. When i dispatch an action, the effect is not immediate. I need to do a callback after it completes the action. How do i do this?
this is the problem
console.log(this.props.items.length); // 5
this.props.dispatch(removeItem(1));
console.log(this.props.items.length); // 5
i need to be able to do it like this
this.props.dispatch(removeItem(1))
.then(() => this.props.dispatch(anotherAction()));
i am using the redux-thunk middleware. i am also using AsyncStorage & redux-persist
store.js
import { compose, createStore, applyMiddleware } from 'redux';
import { persistStore, autoRehydrate } from 'redux-persist';
import thunk from 'redux-thunk';
import reducers from '../reducers';
import { AsyncStorage } from 'react-native';
import createLogger from 'redux-logger';
const logger = createLogger({
predicate: () => process.env.NODE_ENV === 'development'
});
const middleWare = [ thunk, logger ];
const createStoreWithMiddleware = applyMiddleware(...middleWare)(createStore);
export function makeStore(onComplete :? () => void) {
const store = autoRehydrate()(createStoreWithMiddleware)(reducers);
persistStore(store, {
storage: AsyncStorage
}, onComplete);
return store;
}
export default makeStore;
extra code:
function removeItem(id) {
return {
type: 'REMOVE_ITEM',
id
}
}
since removeItem( id ) is an action, that action will remove the item from your database/api/storage. It will then have to dispatch another action to say it is done, like this:
function removeItemSuccess( id ) {
return { type: 'ITEM_REMOVE_SUCCESS', payload: id });
}
function removeItemFail( error ) {
return { type: 'ITEM_REMOVE_FAIL', payload: error });
}
function removeItem ( id ) {
return (dispatch) => {
dispatch({type: 'ITEM_REMOVE'}); // put this in an action
return request.delete('/item/'+id).then(() => {
dispatch( removeItemSuccess(id) );
}).catch((err) => {
dispatch( removeItemFail(err) );
});
}
}
in your reducer, you will now listen for ITEM_REMOVE_SUCCES or ITEM_REMOVE_FAIL to mutate your state.
You shouldn't really put the business-logic in your components since it's bad practice (even if redux-thunk allows you to, possibly).
If you need an action that first removes the item and then, let's say, removes to owner of that item, make a compound function:
function removeItemAndOwner( itemId ) {
return (dispatch) => {
dispatch(removeItem(itemId))
.then((item) => {
return dispatch(removeOwner(item.ownerId))
});
}
}
you can use Promise in react native to make async actions. For example:
let test=[0,1,2];
console.log(test.length);
let myFirstPromise = new Promise((resolve, reject) => {
setTimeout(function(){
test.splice(1);
resolve();
}, 250);
});
myFirstPromise.then(() => {
console.log( test.length);
});
Update:
For your case it will be something like below:
console.log(this.props.items.length); // 5
let myFirstPromise = new Promise((resolve, reject) => {
this.props.dispatch(removeItem(1, resolve));
});
myFirstPromise.then(() => {
console.log(this.props.items.length); // 4
});
remember to return the resolve from removeItem:
function removeItem(id, callback) {
callback();
return {
type: 'REMOVE_ITEM',
id
}
}
you can use reject for the case of error happening.