redux-thunk not dispatching with fetch, but is with axios - redux

Since nock is compatible with axios, and moxios doesn't have much documentation, I decided to use fetch. However now my actions aren't being dispatched.
import { thunk } from 'redux-thunk';
import axios from 'axios';
import {
CURRENT_CONDITION_SUCCESS,
CURRENT_CONDITION_ERROR,
LOAD,
} from './types';
export function initializeLoad() {
return (dispatch) => {
dispatch({type: LOAD, bool: true})
fetch("http://api.wunderground.com/api/777ba6b403bf06b7/geolookup/q/autoip.json")
.then(res => dispatch(getCurrentConditions(res.data.location.l)))
.catch(err => dispatch({type: CURRENT_CONDITION_ERROR, error: err}));
}
}
export function getCurrentConditions(location) {
return (dispatch, getState) => {
fetch(`http://api.wunderground.com/api/777ba6b403bf06b7/conditions/${location}.json`)
.then(condition => dispatch({type: CURRENT_CONDITION_SUCCESS, condition: condition.data.current_observation, input: ''}))
.then(() => {
if(getState.isLoading) {
setTimeout(() => {
dispatch({type: LOAD, bool: false})
}, 2000)
}
})
.catch(err => dispatch({type: CURRENT_CONDITION_ERROR, error: err.data}));
}
}
Any idea why?
Checking the network tab, I know I'm getting a response of 200 with all the data I'm expecting, but dispatch(getCurrentConditions) isn't getting called.
However, if I replace fetch with axios.get, everything works.

Related

Why my dispatch function in Redux gets a function instead of an action?

There is such kind of code that I have:
const mapStateToProps = (state, ownProps) => ({
historyData: getHistoryForSavedVariants(state)[ownProps.savedVariant.variantId],
isHistoryLoading: getHistoryLoading(state),
})
const mapDispatchToProps = (dispatch, ownProps) => ({
loadData: () => {
-----> dispatch(loadHistoryForSavedVariant(ownProps.savedVariant))
},
})
export default connect(mapStateToProps, mapDispatchToProps)(HistoryButton)
In another file loadHistoryForSavedVariant is the following:
export const loadHistoryForSavedVariant = (savedVariant) => {
return (dispatch) => {
dispatch({ type: REQUEST_HISTORY })
const url = `/api/saved_variant/${savedVariant.variantId}/saved_variant_history`
new HttpRequestHelper(url,
(responseJson) => {
dispatch({ type: RECEIVE_HISTORY })
dispatch({ type: RECEIVE_DATA, updatesById: responseJson })
},
(e) => {
dispatch({ type: RECEIVE_HISTORY })
dispatch({ type: RECEIVE_DATA, error: e.message, updatesById: {} })
},
).get({ xpos: savedVariant.xpos, ref: savedVariant.ref, alt: savedVariant.alt, familyGuid: savedVariant.familyGuids[0] })
}
}
So, as can be seen dispatch ultimately gets a function - (dispatch) => {...} and not an action. Why? I don't understand how that works. On Redux official webpage I see everwhere that dispatch gets an action and not a function, so I am confused. The code is, of course, working fine, I am just interested in this particular mechanism, in whats happening here.
That is a "thunk function". Thunks are a Redux middleware that allow you to pass functions into dispatch(), which is useful for writing async logic separate from your components.
For more details, see these Redux tutorials:
https://redux.js.org/tutorials/fundamentals/part-6-async-logic
https://redux.js.org/tutorials/essentials/part-5-async-logic

Redux Dispatch Infinite Loop

I am using axios.get in my useeffect and then I am passing the data from response to the dispatcher. It retrieves data and dispatches and then I get the state to show data an console it but data shows infinite loop. Here is my useEffect, local state, mapStateToProps and mapDispatchToProps.
const [users, setUsers] = useState()
useEffect(() => {
axios.get('https://jsonplaceholder.typicode.com/users').then(response => {
// console.log(response.data)
__storeUsers(response.data)
})
setUsers(showUsers)
console.log(users)
}, [__storeUsers, showUsers, users, setUsers])
const mapStateToProps = state => ({
showUsers: state.getUsers.users
})
const mapDispatchToProps = dispatch => ({
__storeUsers: (data) => dispatch({type: types.STORE_USERS, payload: data}),
})
This is my reducer for users
import * as types from "./types";
const initialState = {
users: []
}
const usersState = (state = initialState, action) => {
switch (action.type) {
case types.STORE_USERS:
return {
...state,
users: action.payload
}
default:
return state
}
}
export default usersState
This is for practice purpose. i am not using actionCreators right now. After this I will move the axios call to the action creator. The data that I get from above goes in loop in console. Please help.
Also if I create action creator for this, that also goes in loop. Action creator is like below:
export const UserActions = () => async (dispatch) => {
const response = await axios.get('https://jsonplaceholder.typicode.com/users')
if (response.data) {
// console.log(response.data)
dispatch({
type: types.STORE_USERS,
payload: response.data
})
} else {
// console.log("no data")
}
return response
}
And then I use it like below
const mapDispatchToProps = dispatch => ({
__storeUsers: () => dispatch(UserActions())
})
Both methods are firing loop in console.
Notice that your useEffect here is where the infinite loop occurs:
const [users, setUsers] = useState()
useEffect(() => {
axios.get('https://jsonplaceholder.typicode.com/users').then(response => {
// console.log(response.data)
__storeUsers(response.data)
})
setUsers(showUsers)
console.log(users)
}, [__storeUsers, showUsers, users, setUsers])
The useEffect has been told that users is one of its dependencies and that it should re-run when this variable changes. The useEffect then changes the value of users via setUsers and it sees this update so runs again.
It looks like you're only depending on users for this console.log. Consider taking it out of the dependency list.

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.

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))
});
}
};

How use data from store in fetch action (react-redux)

How can I made fetch chain async actions where second fetch is using data from first? I need fetch repository list (GitHub API) and then fetch users from those repos. I made this:
export function reposListFetchData(url) {
return (dispatch) => {
dispatch(reposListIsLoading(true))
fetch(url)
.then((response) => {
if(!response.ok){
throw Error(response.statusText)
}
dispatch(reposListIsLoading(false))
return response
})
.then((response) => response.json())
.then((repos) => dispatch(reposListFetchSuccess(repos)))
.then( this.props.repos.map(
repo=>this.props.fetchContributorsData(`https://api.github.com/repos/angular/${repo.name}/contributors?per_page=100`)
))
.catch(()=> dispatch(reposListHasErrored(true)))
}
}
but of course I cant use this.props there. Any suggestions?
Assuming fetchContributorsData is an action that is quite similar with the reposListFetchData, you should be able to do this...
export function reposListFetchData(url) {
return dispatch => {
dispatch(reposListIsLoading(true));
fetch(url)
.then(response => {
if (!response.ok) {
throw Error(response.statusText);
}
dispatch(reposListIsLoading(false));
return response;
})
.then(response => response.json())
.then(repos => {
dispatch(reposListFetchSuccess(repos));
// where repos is an array of repo
repos.map(repo =>
dispatch(fetchContributorsData(`https://api.github.com/repos/angular/${repo.name}/contributors?per_page=100`))
);
})
.catch(() => dispatch(reposListHasErrored(true)));
};
}

Resources