trouble setting up redux thunk - redux

I have the following action creator:
export function selectBook(ISBN, dispatch) {
const url = `${ROOT_ITEMS_URL}/${ISBN}?all=true`;
dispatch({ type: 'SELECT_BOOK_LOADING', isLoading:true });
axios.get(url)
.then(({ data }) => dispatch({ type: 'SELECT_BOOK_SUCCESS', data}))
.catch(( err ) => dispatch({ type: 'SELECT_BOOK_FAILURE', isLoading:false}))
}
I also have the following in my main index file for my project:
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import LoginLayout from './layouts/LoginLayout';
import reducers from './reducers';
import ReduxPromise from 'redux-promise';
import reduxThunk from 'redux-thunk';
const createStoreWithMiddleware = applyMiddleware(ReduxPromise, reduxThunk)(createStore);
//const createStoreWithMiddleware = applyMiddleware()(createStore);
ReactDOM.render(
<Provider store={createStoreWithMiddleware(reducers)}>
<LoginLayout />
</Provider>
, document.querySelector('.root'));
I am confused on how to get dispatch called into my project. I am getting the following error:
bundle.js:5690 Uncaught TypeError: dispatch is not a function
When I call this function I am not passing any dispatch. But I am not sure how to call dispatch into it. Is it a redux call I need to make with my function call?
this.props.selectBook(params.bookID);
Should it be this instead?:
this.props.selectBook(params.bookID, dispatch);
and what import is dispatch with? import dispatch from 'redux'?

dispatch is given to you for free via redux-thunk. The trick here is that your action creator should return a function, and the first argument to the function will be dispatch. Your action creator will end up looking like this:
export function selectBook(ISBN) {
return dispatch => {
const url = `${ROOT_ITEMS_URL}/${ISBN}?all=true`;
dispatch({ type: 'SELECT_BOOK_LOADING', isLoading:true });
axios.get(url)
.then(({ data }) => dispatch({ type: 'SELECT_BOOK_SUCCESS', data}))
.catch(( err ) => dispatch({ type: 'SELECT_BOOK_FAILURE', isLoading:false}))
}
}

Try to do your action creator like that:
export function selectBook(ISBN) {
const url = `${ROOT_ITEMS_URL}/${ISBN}?all=true`;
return dispatch => {
dispatch({ type: 'SELECT_BOOK_LOADING', isLoading:true });
axios.get(url)
.then(({ data }) => dispatch({ type: 'SELECT_BOOK_SUCCESS', data}))
.catch(( err ) => dispatch({ type: 'SELECT_BOOK_FAILURE', isLoading:false}))
}
}
It needs to return function

Related

React-redux Toolkit: Cannot set new state, when passing reducer as prop to another function

I am trying to use react redux toolkit and pass setter function to set new state on firebase's 'onAuthStateChanged'. The plan was to pass user's state (object or null) to reducer, depending if user is logged in or logged out. This is my first usage of redux, so I can't get why my code doesn't work. There is no errors, but in redux devtools state is always equal to null.
Configure Store:
import { configureStore } from '#reduxjs/toolkit'
import { Provider } from 'react-redux';
import userReducer from './utils/userReducer';
const store = configureStore({
reducer: {
user: userReducer,
}
})
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);
My reducer:
import { createSlice } from "#reduxjs/toolkit";
export const userSlice = createSlice({
name: 'user',
initialState: null,
reducers: {
setUser: (state, action) => {
state = action.payload;
}
}
})
export const {setUser} = userSlice.actions;
export default userSlice.reducer;
Where I am dispatching it:
import { setUser } from '../utils/userReducer'
import { useDispatch } from 'react-redux'
const dispatch = useDispatch()
const handleLogin = async (e) => {
e.preventDefault()
const { user } = await logInWithEmail(email, password)
await setCurrentUser(() => dispatch(setUser))
}
Firebase function, where I am trying to use reducer:
export const setCurrentUser = async (setUser) => {
await onAuthStateChanged(auth, (currentUser) => {
setUser(currentUser)
})
}
I understand, that with useContext it would be much easier, but I am trying to learn redux by implying it.
Try like that:
import { setUser } from '../utils/userReducer'
import { useDispatch } from 'react-redux'
const dispatch = useDispatch()
const handleLogin = async (e) => {
e.preventDefault()
const { user } = await logInWithEmail(email, password)
// This line updated
await setCurrentUser((currentUser) => dispatch(setUser(currentUser)))
}
The reason:
your setCurrentUser function prop setUser is just function () => dispatch(setUser), but this function does not receive any prop, and dispatch(setUser) does not do anything. you need to pass value (payload) to reducer function.
Additionally, try passing dispatch itself as prop and dispatch inside of onAuthStateChanged.
export const setCurrentUser = async (dispatch) => {
await onAuthStateChanged(auth, (currentUser) => {
dispatch(setUser(currentUser))
})
}
import setUser reducer function if handleLogin and setCurrentUser function is in different files separately.

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

I've read multiple sources about this error but I cannot figure out what I'm doing incorrectly here. I'm using custom middleware already and I believe that I'm returning the action correctly. Any advice?
app.js
import React from "react";
import ReactDOM from "react-dom";
import { renderToString } from "react-dom/server";
import { Provider } from "react-redux";
import { createStore, applyMiddleware, compose } from "redux";
import thunk from 'redux-thunk';
import rootReducer from '../reducers';
import DataProvider from "./DataProvider";
import QuestionContainer from "./QuestionContainer";
import * as actions from "../actions";
const App = () => <QuestionContainer />;
const store = createStore(
rootReducer,
applyMiddleware(thunk)),
);
store
.dispatch(actions.fetchQuestions())
.then(() => response.send(renderToString(<Provider store={ store }><App /></Provider>)))
Then in actions.js
export function fetchQuestions() {
return (dispatch) => {
return fetch('/api/questions')
.then(response => response.json())
.then(data => dispatch(loadRequestData(data)),
)
}
}
The error showing in browser console:
redux.js:208 Uncaught (in promise) Error: Actions must be plain objects. Use custom middleware for async actions.
at dispatch (redux.js:208)
at eval (index.js:12)
at dispatch (redux.js:571)
at eval (actions.js:35)
I think there's something's wrong in this part of code:
store
.dispatch(actions.fetchQuestions())
.then(() => response.send(renderToString(<Provider store={ store }><App /></Provider>)))
When you're creating async calls you want to do this only in action, not reducer/store.
So you need to delete this line .then(() => response.send(renderToString(<Provider store={ store }><App />
and instead of that just make:
const app = (
<Provider store={store}>
<App />
</Provider>
)
ReactDOM.render(app, document.getElementById('root'));
Additionally make some actions which will be kind of helper for updating store in your reducer. Something like this:
export const fetchBegin = () => ({
type: 'FETCH_BEGIN'
})
export const fetchSuccess = (payload) => ({
type: 'FETCH_SUCCESS'
payload
})
export const fetchQuestions = () => {
return (dispatch) => {
dispatch(fetchBegin())
return fetch('/api/questions')
.then(response => response.json())
.then(data => dispatch(fetchSuccess(data))
)
}
}
Then in the reducers make:
const initialState = {
call: [],
loading: false
}
const reducer = (state = initialState, action){
switch(action.type){
case 'FETCH_BEGIN:
return{
...state,
loading: true,
case 'FETCH_SUCCESS':
return{
...state,
call: action.payload,
loading: false,
}
default:
return state
}
}
This should work imho.

Why does Redux Promise Middleware not dispatch a rejected action for my example code?

I use fetch-mock, redux-mock-store, promise-middleware to test the redux implementation of my application. I have following code:
import configureMockStore from 'redux-mock-store';
import promiseMiddleware from 'redux-promise-middleware';
import fetchMock from 'fetch-mock';
import thunk from 'redux-thunk';
import createLogger from 'redux-logger';
import { bindActionCreators } from 'redux';
import { ACTION_1, hostNameSearchActions }
from '../../../src/actions/hostNameSearchActions';
const middlewares = [thunk, promiseMiddleware(), createLogger()];
let mockStore = configureMockStore(middlewares);
const SERVICE_URL = 'http://url_to_the_service';
describe('Testing thunk actions', () => {
let store = mockStore({ hostData: { key1 :'value'} });
const aHostNameSearch = bindActionCreators({ ...hostNameSearchActions }, store.dispatch).hostNameSearch;
afterEach(() => {
fetchMock.reset();
fetchMock.restore();
mockStore = configureMockStore(middlewares);
store = mockStore({ hostData: { key1 :'value'} });
});
it('ACTION_1_PENDING, ACTION_1_REJECTED dispatched, payload matches expected payload', (done) => {
fetchMock
.mock(`${SERVICE_URL}`,
404 );
const expectedActions = [
{ type: `${ACTION_1}_PENDING` },
{ type: `${ACTION_1}_REJECTED`, payload: {error: 'test.body.error.message'}}
];
aHostNameSearch().then(() => {
expect(store.getActions()).toEqual(expectedActions);
done();
});
});
});
The problem is that 404 call I am mocking with retchMock always ends up being resolved as ACTION_1_FULFILLED. why would this be the case? Am I mocking the call incorrectly?
Redux Promise Middleware always dispatches a rejected action when given a rejected action. If your mocked action always ends up being a fulfilled action, when you expect a rejected action, it is because the promise payload is fulfilled.
This can happen if you have any side-effects (e.g., any functions that use the then method on the promise) and don't properly pass the error up to the middleware. Without more context, though, it's impossible to give you a definitive answer. It would be helpful if you included your hostNameSearchActions.

redux Async Action Error: Actions must be plain objects. Use custom middleware for async actions

I used redux thunkMiddle to implement async action, but it was error when I send Http request in actions, the err is :
VM711:3 Uncaught Error: Actions must be plain objects. Use custom middleware for async actions.
at Object.performAction (<anonymous>:3:2312)
at liftAction (<anonymous>:2:27846)
at dispatch (<anonymous>:2:31884)
at Object.dispatch (bundle.js:22661)
at dispatch (<anonymous>:2:1620)
at Object.submitForm (bundle.js:23120)
at Form.submitForm (bundle.js:23168)
at Object.ReactErrorUtils.invokeGuardedCallback (bundle.js:4532)
at executeDispatch (bundle.js:4332)
at Object.executeDispatchesInOrder (bundle.js:4355)
There is my code:
In my action,I use superagent to send request,my code like this:
import superagent from 'superagent'
import async from 'async'
export const onSubmitForm = userInfo => {
async.waterfall([
(done) => {
superagent
.post('/userInfo')
.send(userInfo)
.end((err, res) => {
done(err, res.body)
});
}
], (err, data) => {
return (dispatch) => (dispatch(submitFormAction(data)))
});
};
export const submitFormAction = data => {
return {
type: "USER_INFO",
data
}
};
And This is my entry file,I import thunkMiddle from redux :
import React from 'react';
import {render} from 'react-dom';
import {createStore, applyMiddleware} from "redux";
import { composeWithDevTools } from 'redux-devtools-extension';
import {Provider} from "react-redux";
import reducer from './reducers/index';
import thunkMiddleware from 'redux-thunk';
import {App} from './containers/App';
const store = createStore(reducer, composeWithDevTools(applyMiddleware(thunkMiddleware)));
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root'));
So,how to solve this problem?
A thunk must return a function - some of your code-paths result in nothing being returned.
try altering your action by wrapping it in a function which you can return:
export const onSubmitForm = userInfo => {
return function(dispatch) {
async.waterfall([
(done) => {
superagent
.post('/userInfo')
.send(userInfo)
.end((err, res) => {
done(err, res.body)
});
}
], (err, data) => {
dispatch(submitFormAction(data))
});
}
};

Reducer can't receive data from action creators

I've got a problem with my reducer. I have created an action creator, reducer, and used 'react-redux' connect to combine both.
When I'm fireing my action, action creator logs, that he just received a new data, but reducer does not log anything (Reducer logs only 3 initial loops). Also, store.getState() which I'm display in console every 5 seconds shows null (which is initial state of my store). Could you help me to deal with the problem?
export const UPDATE_PACKAGE_JSON = 'UPDATE_PACKAGE_JSON';
export function setName(name){
console.log('Action creator just received a name', name);
return {
type: UPDATE_PACKAGE_JSON,
payload: name
}
}
Container
const mapDispatchToProps = (dispatch) =>
bindActionCreators({ setName }, dispatch);
export default connect(null, mapDispatchToProps)(ConfigurationForm) ;
Reducer
import { UPDATE_PACKAGE_JSON } from './../actions/index';
export const packageJson = function (state = null, action){
console.log(UPDATE_PACKAGE_JSON);
switch(action.type){
case UPDATE_PACKAGE_JSON:
return {...state,
name: action.payload};
}
return state;
};
EDIT
Store
import { createStore } from 'redux';
import rootReducer from './../reducers/rootReducer';
const store = createStore(rootReducer);
export default store;
rootReducer
import { combineReducers } from 'redux';
import { packageJson } from './packageJson';
const rootReducer = combineReducers({
packageJson
});
export default rootReducer;

Resources