React-Redux: Actions must be plain objects. Use custom middleware for async actions
Asked Answered
S

26

152

Unhandled Rejection (Error): Actions must be plain objects. Use custom middleware for async actions.

I wanted to add comments with every posts. So when fetch posts are run I want to call fetch comment API for all post.

export function bindComments(postId) {
  return API.fetchComments(postId).then(comments => {
    return {
      type: BIND_COMMENTS,
      comments,
      postId
    }
  })
}
Scottyscotus answered 16/10, 2017 at 8:23 Comment(0)
C
101

You have to dispatch after the async request ends.

This would work:

export function bindComments(postId) {
    return function(dispatch) {
        return API.fetchComments(postId).then(comments => {
            // dispatch
            dispatch({
                type: BIND_COMMENTS,
                comments,
                postId
            });
        });
    };
}
Cowes answered 16/10, 2017 at 8:43 Comment(3)
It's mentioned down below, but you need to have redux-thunk middleware for this to workMount
@Cowes why is it mandatory to dispatch an action? I had the same problem with an async action I didn't define as "async dispatch" although I didn't want to dispatch anything to the store.Blindly
I have already answered how will redux-thunk will work to solve this issue. Copy my username and find the solution somewhere on this page.Parley
A
58

For future seekers who might have dropped simple details like me, in my case I just have forgotten to call my action function with parentheses.

actions.js:

export function addNewComponent() {
  return {
    type: ADD_NEW_COMPONENT,
  };
}

myComponent.js:

import React, { useEffect } from 'react';
import { addNewComponent } from '../../redux/actions';

  useEffect(() => {
    dispatch(refreshAllComponents); // <= Here was what I've missed.
  }, []);

I've forgotten to dispatch the action function with (). So doing this solved my issue.

  useEffect(() => {
    dispatch(refreshAllComponents());
  }, []);

Again this might have nothing to do with OP's problem, but I hope I helps people with the same problem as mine.

Alcala answered 19/2, 2020 at 7:18 Comment(1)
Thanks for this. Easy to miss. Not doing any async stuff so I knew it was something simple I missed!Cocytus
P
39

The error is simply asking you to insert a Middleware in between which would help to handle async operations.

You could do that by :

npm i redux-thunk

        Inside index.js

import thunk from "redux-thunk" 
import { createStore, applyMiddleware } from 'redux';
        
...createStore(rootReducers, applyMiddleware(thunk));

Now, async operations will work inside your functions.

Parley answered 10/6, 2020 at 11:25 Comment(1)
I was using createSlice with asyncThunk and after so many questions and answers, your answer worked. Thanks.Lymphoblast
S
14

You can't use fetch in actions without middleware. Actions must be plain objects. You can use a middleware like redux-thunk or redux-saga to do fetch and then dispatch another action.

Here is an example of async action using redux-thunk middleware.

export function checkUserLoggedIn (authCode) {
 let url = `${loginUrl}validate?auth_code=${authCode}`;
  return dispatch => {
    return fetch(url,{
      method: 'GET',
      headers: {
        "Content-Type": "application/json"
      }
      }
    )
      .then((resp) => {
        let json = resp.json();
       if (resp.status >= 200 && resp.status < 300) {
          return json;
        } else {
          return json.then(Promise.reject.bind(Promise));
        }
      })
      .then(
        json => {
          if (json.result && (json.result.status === 'error')) {
            dispatch(errorOccurred(json.result));
            dispatch(logOut());
          }
          else{
            dispatch(verified(json.result));
          }
        }
      )
      .catch((error) => {
        dispatch(warningOccurred(error, url));
      })
  }
}
Sphygmic answered 16/10, 2017 at 8:32 Comment(1)
Sorry I thought you are not using middleware. it was dispatch problem there that @Cowes mentioned it.Sphygmic
S
8

Change:

export const <youractionName> = async (dispatch) => {}

to,

export const <youractionName> = () => async (dispatch) => {}

This fixed my issue. Missed a '() =>'

Sierrasiesser answered 20/3, 2021 at 15:27 Comment(0)
C
7

Make use of Arrow functions it improves the readability of code. No need to return anything in API.fetchComments, Api call is asynchronous when the request is completed then will get the response, there you have to just dispatch type and data.

Below code does the same job by making use of Arrow functions.

export const bindComments = postId => {
  return dispatch => {
    API.fetchComments(postId).then(comments => {
      dispatch({
        type: BIND_COMMENTS,
        comments,
        postId
      });
    });
  };
};
Calcareous answered 19/9, 2018 at 5:37 Comment(0)
S
2

I had same issue as I had missed adding composeEnhancers. Once this is setup then you can take a look into action creators. You get this error when this is not setup as well.

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;

const store = createStore(
  rootReducer,
  composeEnhancers(applyMiddleware(thunk))
);
Stancil answered 27/6, 2020 at 23:24 Comment(0)
G
2

I have solved my issue changing :

export const = async (dispatch) => {}

to,

export const = () => async (dispatch) => {}

Gilgilba answered 14/5, 2022 at 10:19 Comment(1)
I've done the same, and indeed the error disappeared. But there is now an error on the last dispatch I use in rather the Catch to Finally block: dispatch is not a function.. Why?Bracey
I
1

Use redux-thunk, setup with redux & create action like this

export const actionName = (data) => dispatch => {
  dispatch({
    type:"ACTION_TYPE",
    payload:"my payload"
  })
}
Idomeneus answered 23/7, 2021 at 11:52 Comment(0)
B
1

You might also have forgotten to getDefaultMiddleware() in the middlewares' array, as I did. No further installations required:

export const store = configureStore({
  reducer: GlobalReducer,
  middleware: (getDefaultMiddleware) => [
    ...getDefaultMiddleware(),
    mainMiddleware,
  ],
});
Bitty answered 14/11, 2021 at 20:57 Comment(0)
C
1

Without middleware, redux supports only synchronous data flow. If you need to make ajax request and dispatch the result of this request, then you need to use middlewares that handles the async operations like, redux-promise, redux-thunk or redux-saga. Or you could write your own middleware:

export default ({ dispatch }) =>
  (next) =>
  (action) => {
    // check if there is payload in  action. if not send it to the next middleware
    if (!action.payload || !action.payload.then) {
      return next.action;
    }
    // if we are here means we have action.payload. now check if it is promise
    // wait for the promise to be resolved
    action.payload.then(function (response) {
      // overwrite the action
      const newAction = { ...action, payload: response };
      dispatch(newAction);
    });
  };
Canvasback answered 24/4, 2022 at 23:36 Comment(0)
W
1

For those who are still facing the error, this might solve the issue

import { configureStore } from '@reduxjs/toolkit'

import logger from 'redux-logger'

import rootReducer from './reducer'

const store = configureStore({
  reducer: rootReducer,
  middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(logger),
})
Womanly answered 24/3, 2024 at 7:41 Comment(0)
A
0

Action Definition

const selectSlice = () => {
  return {
    type: 'SELECT_SLICE'
  }
};

Action Dispatch

store.dispatch({
  type:'SELECT_SLICE'
});

Make sure the object structure of action defined is same as action dispatched. In my case, while dispatching action, type was not assigned to property type.

Alsatian answered 4/8, 2020 at 3:57 Comment(0)
W
0

If you are working with redux-observable check that your action returns an observable. I had the issue because I used map and not a mergemap

// error
    export const myEpic = (action$: any) =>
      action$.pipe(
        ofType('...'),
        map((x => x.payload),
        map((x) => callAPi(x)),
      )
    
// success
        export const myEpic = (action$: any) =>
          action$.pipe(
            ofType('...'),
            map((x => x.payload),
            mergeMap((x) => callAPi(x)),
          )
Whyte answered 14/5, 2021 at 1:13 Comment(0)
H
0

Just here to share my case. I had a setLoading action, while also having

const [loading, setLoading] = useState(false)

above which I didn't delete. So it was basically not dispatching the setLoading from redux but the one from useState. Deleting/renaming this solves the problem.

Hummocky answered 5/8, 2021 at 8:23 Comment(0)
P
0

if things were working with this code and this is a new iteration, check to make sure you have your variables in the correct order for the function (this was my mistake)

i.e. code that got this error

export const fetchProjects = newPage => (getState, dispatch) => NOPE

export const fetchProjects = newPage => (dispatch, getState) => OK YEAH
Peptonize answered 23/9, 2021 at 20:43 Comment(1)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Reify
E
0

In my case, I just wanted to sent some values to the server without saving them to redux store, so I was not using a type, nor dispatching anything at the end. But I was calling the action with dispatch. So all I had to do, was to remove the dispatch, because it wasn't really an action. It was just a function.

Eparch answered 30/9, 2021 at 13:30 Comment(0)
D
0

For me, the solution was to add redux-thunk as a middleware, so inside my store configuration file, I passed redux-thunk as middleware.

inside the console:

enter image description here

import reducerData from './store-reducer';
import {applyMiddleware, compose, createStore} from 'redux';
import ReduxThunk from 'redux-thunk';

const middlewares = [ReduxThunk];

const store = createStore(
  reducerData,
  compose(applyMiddleware(...middlewares)),
);
export default store;
Decongestant answered 26/12, 2021 at 12:15 Comment(1)
not working for meArboriculture
J
0

Arrow function syntax

export const bindComments = (postId) => dispatch => {
 return API.fetchComments(postId).then(comments => {
   // dispatch
    dispatch({
      type: BIND_COMMENTS,
       comments,
       postId
   })
})}
Jordanson answered 27/12, 2021 at 0:27 Comment(0)
E
0

This error occurs mainly if you are dispatching an action and your action is not returning an object. For example here is an increment function which I use it to increment number value when increment button is clicked. const increment = () => type: INCREMENT and here is my dispatch function onClick={() => dispatch(increment)} because of ommiting parenthesis () inside dispatch function now in your terminal there would be the same error appears. The reason dispatch function expects an object not a function name...

Eldin answered 30/12, 2021 at 18:7 Comment(0)
K
0

This error occurs when you make an asynchronous api call in your action creator you need to convert your action creator from synchornous action creator to asynchronous action and this conversion can be possible if we use the middleware so let me explain you in detailRedux without middleware

Two types of action creators Sync Action Creator VS Async Action Creator. you need to change sync action to async action in order to get rid of this error and this can be done with middleware

Redux with middleware enter image description here

So now solution is: Dispatch after the async request would befinished.

export function bindComments(postId) {
        return function(dispatch) {
            return API.fetchComments(postId).then(comments => {
                // dispatch
                dispatch({
                    type: BIND_COMMENTS,
                    comments,
                    postId
                });
            });
        };
    }  
Katelin answered 26/3, 2022 at 12:40 Comment(0)
M
0

Whenever you wish to perform async operation with redux shore you must use middleware ex. redux-thunk

Error:

action.js

export const login = () => async (dispatch, getState) => {}

sore.js

import reducerData from './store-reducer';
import {createStore} from 'redux';

const middlewares = [ReduxThunk];

const store = createStore(
  reducerData,
);
export default store;

Solution:

import reducerData from './store-reducer';
import {applyMiddleware, compose, createStore} from 'redux';
import ReduxThunk from 'redux-thunk';

const middlewares = [ReduxThunk];

const store = createStore(
  reducerData,
  compose(applyMiddleware(...middlewares)),
);
export default store;
Morrell answered 22/6, 2022 at 6:7 Comment(0)
E
0

A reason for this error I had that caught me off guard—

Dispatch was failing in a test even though the action creator was synchronous. Turned out I was mocking the action creator and this was the error it gave when it wasn't returning because of that.

Ericerica answered 21/7, 2022 at 14:37 Comment(0)
K
0

this works for me.... To handle asynchronous actions in Redux, you can use middleware such as redux-thunk, redux-saga, or redux-observable. These middleware libraries allow you to dispatch functions from action creators, which can then perform asynchronous operations (such as making API requests) and dispatch additional actions when the operation is complete.

`import { createStore, combineReducers, applyMiddleware } from "redux";
 import thunk from "redux-thunk";
 export const store = createStore(rootReducer, applyMiddleware(thunk));`
Kathikathiawar answered 17/4, 2023 at 7:42 Comment(0)
E
0

In my case i actually called the dispatch method without passing any function something like below which caused me above error.

dispatch();

Resolved it by passing a function

Egyptology answered 7/10, 2023 at 19:1 Comment(0)
B
0

This error could also mean that your are trying to dispatch a redux action that does not exist eg

    const generalSlice = createSlice({
     name: 'general',
     initialState,
     reducers: {
      setDropdownSelectedDateRedux: (state, action) => {
       state.dropdownSelectedDateRedux = action.payload;
      },
     },
     extraReducers: (builder) => {
      // get all data daily
      builder.addCase(triggerGetAllDataDaily.pending, (state) => {
      state.getAllDataDaily.status = states.LOADING;
      state.getAllDataDaily.data = [];
      });
      builder.addCase(triggerGetAllDataDaily.fulfilled, (state: any, 
       action) => {
      state.getAllDataDaily.status = states.SUCCESSFUL;
      state.getAllDataDaily.data = action.payload;
      });
      builder.addCase(triggerGetAllDataDaily.rejected, (state) => {
      state.getAllDataDaily.status = states.ERROR;
      state.getAllDataDaily.data = [];
      });
     },
    });

Note that the name of the reducer here is setDropdownSelectedDateRedux

But then when you try to dispatch a wrong reducer eg

const dispatch=useDispatch<any>()

 useEffect(() => {
  dispatch(
   setDropdownSelectedDate({
    title: dropdownSelectedDate.value,
    value: dropdownOptionsDate.value,
   })
  );
 }, []);

You will encounter this error

Badge answered 30/4, 2024 at 9:43 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.