I am having difficulty updating my store after calling an API. I am using reduxjs/toolkit. Here is the structure of the project:
react/
store/
api/
dataconsumer/
dataSlice.js
notifications/
notificationsSlice.js
app.js
Here, api contains non-component API calls to the server. They are bound to thunk functions within dataSlice and a successful query updates data just fine.
The following are relevant parts to my reducers.
notificationSlice.js
const slice = createSlice({
...,
reducers: {
// need to call this from api
setNotifications: (state, action) => state.notification = action.payload
}
})
dataSlice.js
export const fetchInitialData = createAsyncThunk(
'chart/fetchInitialData',
async (data) => {
return API.candles.initialData({
...data
})
}
const slice = createSlice({
...
extraReducers: {
...
[fetchInitialData.success]: (state, action) => state.action = action.payload
}
})
And the api
const fetchInitialData = () => {
return fetch(url, {
...
}).then(data => data.json())
.then(data => {
if(data.status === 200) { return data } // works great!
else {
// doesn't work, but functionally what I'm looking for
store.dispatch(setNotifications(data.status))
}
})
}
The problem is when the response is other than 200, I need to update notifications, but I don't know how to get the data to that reducer.
I can't useDispatch because it is outside a component, and if I import the store to my api files it is outside the context provider and my state is uninitialized.
I'm sure I could use localStorage to solve the problem or some other hack, but I feel I shouldn't have to and I'm wondering if there is a key principle I'm missing when organizing my react-redux project? or if there are standard solutions to this problem.
Thanks - I'm new to redux.
Well, if you are using thunk, then the best solution will be to use it in order to dispatch your action after you get the error.
You do it like this:
export const fetchInitialData = () => {
return dispatch => {
...your logic
else {
// now you can dispatch like this
dispatch(setNotifications(data.status))
}
}
};
I am using redux-toolkit with createAsyncThunk to handle async requests.
I have two kinds of async operations:
get the data from the API server
update the data on the API server
export const updateData = createAsyncThunk('data/update', async (params) => {
return await sdkClient.update({ params })
})
export const getData = createAsyncThunk('data/request', async () => {
const { data } = await sdkClient.request()
return data
})
And I add them in extraReducers in one slice
const slice = createSlice({
name: 'data',
initialState,
reducers: {},
extraReducers: (builder: any) => {
builder.addCase(getData.pending, (state) => {
//...
})
builder.addCase(getData.rejected, (state) => {
//...
})
builder.addCase(
getData.fulfilled,
(state, { payload }: PayloadAction<{ data: any }>) => {
state.data = payload.data
}
)
builder.addCase(updateData.pending, (state) => {
//...
})
builder.addCase(updateData.rejected, (state) => {
//...
})
builder.addCase(updateData.fulfilled, (state) => {
//<--- here I want to dispatch `getData` action to pull the updated data
})
},
})
In my component, I have a button that triggers dispatching of the update action. However I found after clicking on the button, despite the fact that the data is getting updated on the server, the data on the page is not getting updated simultaneously.
function MyComponent() {
const dispatch = useDispatch()
const data = useSelector((state) => state.data)
useEffect(() => {
dispatch(getData())
}, [dispatch])
const handleUpdate = () => {
dispatch(updateData())
}
return (
<div>
<ul>
// data goes in here
</ul>
<button onClick={handleUpdate}>update</button>
</div>
)
}
I tried to add dispatch(getData()) in handleUpdate after updating the data. However it doesn't work because of the async thunk. I wonder if I can dispatch the getData action in the lifecycle action of updateData i.e.
builder.addCase(updateData.fulfilled, (state) => {
dispatch(getData())//<--- here I want to dispatch `getData` action to pull the updated data
})
Possibly it's not actual and the question is outdated, but there is thunkAPI as second parameter in payload creator of createAsyncThunk, so it can be used like so
export const updateData = createAsyncThunk('data/update', async (params, {dispatch}) => {
const result = await sdkClient.update({ params })
dispatch(getData())
return result
})
First of all: please note that reducers always need to be pure functions without side effects. So you can never dispatch anything there, as that would be a side effect. Even if you would somehow manage to do that, redux would warn you about it.
Now on to the problem at hand.
You could create a thunk that dispatches & awaits completion of your updateData call and then dispatches your getData call:
export const updateAndThenGet = (params) => async (dispatch) => {
await dispatch(updateData(params))
return await dispatch(getData())
}
//use it like this
dispatch(updateAndThenGet(params))
Or if both steps always get dispatched together anyways, you could just consider combining them:
export const updateDataAndGet = createAsyncThunk('data/update', async (params) => {
await sdkClient.update({ params })
const { data } = await sdkClient.request()
return data
})
I'm going to save in localstorage some data, but only after call UPDATE_POST action. Now i'm apply localstorage in index.js via:
store.subscribe(throttle(() => {
post: saveState(store.getState().post);
}, 1000))
and it save data in localstorage for every second. But my goal is to save it only after updatePost action. Can I achieve it using middleware, and how to write it?
My reducer:
const Post = (state = {}, action) => {
switch (action.type) {
case 'INIT_POST':
..... some code
case 'UPDATE_POST':
... some code
default:
return state
}
};
My action:
export const updatePost = (...items) => ({
type: 'UPDATE_POST',
items
});
I use Redux-thunk for this (https://github.com/gaearon/redux-thunk) - it lets you write action creators that return a function instead of an action - allowing you to perform async tasks in the action, then hit the reducer.
With redux-thunk you can call an async function (performSomeAsyncFunction() in example below), get the response, deal with it (such as the saveDataToLocalStorage() dummy function below), then hit the reducer to update your state:
export const startUpdatePost = (...items) => {
return (dispatch) => {
return performSomeAsyncFunction(...items).then((response) => {
dispatch(updatePost(...items));
saveDataToLocalStorage()
});
};
};
Don't forget to also handle the failure of the async function above
I'm a redux beginner. I'm using redux-thunk, however, I'm only getting error from this function.
Error: Actions must be plain objects. Use custom middleware for async actions.
this.props.sendPaypalOrderToFirebase(body)
export const sendPaypalOrderToFirebase = (orderInfo) => {
async (dispatch, getState) => {
database.ref('paypalOrders/' + uuid()).set({
orderInfo
});
return dispatch(paypalOrderFirebaseSuccess(orderInfo))
}
}
export const createOrder = (paymentMethod, paymentData) => ({
type: actionTypes.CREATE_ORDER,
paymentMethod,
paymentData
});
export const paypalOrderFirebaseSuccess = (orderInfo) => ({
type: actionTypes.PAYPAL_ORDER_FIREBASE_SUCCESS,
orderInfo
})
thanks for your help.
The issue here is your use of async:
export const sendPaypalOrderToFirebase = (orderInfo) => {
async (dispatch, getState) => {
// ...
}
}
What this snippet is actually doing is creating an async arrow function, that never gets called as it is not being returned from the action creator (calling this action creator will just return undefined).
Fixing it can be achieved by either:
Adding in a return:
export const sendPaypalOrderToFirebase = (orderInfo) => {
return async (dispatch, getState) => {
// ...
}
}
Or removing some braces:
export const sendPaypalOrderToFirebase = (orderInfo) => async (dispatch, getState) => {
// ...
}
Finally, it's worth considering if you actually need the async functionality here at all, as you don't appear to be creating a Promise (or at least awaiting one) in the thunk block.
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
}
})
}
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
});
});
};
}
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.
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.
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));
})
}
}
Change:
export const <youractionName> = async (dispatch) => {}
to,
export const <youractionName> = () => async (dispatch) => {}
This fixed my issue. Missed a '() =>'
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
});
});
};
};
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))
);
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,
],
});
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);
});
};
I have solved my issue changing :
export const = async (dispatch) => {}
to,
export const = () => async (dispatch) => {}
Use redux-thunk, setup with redux & create action like this
export const actionName = (data) => dispatch => {
dispatch({
type:"ACTION_TYPE",
payload:"my payload"
})
}
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.
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)),
)
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.
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
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.
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:
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;
Arrow function syntax
export const bindComments = (postId) => dispatch => {
return API.fetchComments(postId).then(comments => {
// dispatch
dispatch({
type: BIND_COMMENTS,
comments,
postId
})
})}
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...
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
});
});
};
}
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;
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.