This is the first time I am working with redux saga. I have a backend route called https://localhost:5000/developers/signup. I have a signup form:
import { FormEvent, useState } from 'react';
import HttpService from 'services/Http';
export default function Signup() {
const [formData, setFormData] = useState({ firstName: '', lastName: '', email: '', password: '' });
const handleSubmit = async (e: FormEvent) => {
e.preventDefault();
try {
const httpService = new HttpService('api/developers/signup');
const res = await httpService.create(formData);
// I receive the user data + JWT token
console.log(res);
} catch (err) {
console.log(err);
}
};
return (
<main>
<h1>Signup</h1>
<form onSubmit={handleSubmit}>
// Some JSX to show the form
</form>
</main>
);
}
Store:
import { applyMiddleware, combineReducers, compose, createStore } from 'redux';
import createSagaMiddleware from 'redux-saga';
import authReducer from './ducks/auth';
import rootSaga from './sagas/root';
const reducers = combineReducers({
auth: authReducer,
});
const sagas = createSagaMiddleware();
const composeSetup =
/*#ts-ignore eslint-disable */
process.env.NODE_ENV !== 'production' && typeof window === 'object' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
? /*#ts-ignore eslint-disable */
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
: compose;
/*eslint-enable */
const middleWare = [sagas];
const store = createStore(reducers, composeSetup(applyMiddleware(...middleWare)));
sagas.run(rootSaga);
export default store;
I am unable to understand what redux saga does, It would be great if someone could explain this. I've seen a lot of posts and youtube video. I looked at the docs but then it did not have a basic AJAX example. I would like to have a redux state structure like this:
{
auth: {
// Some auth data like the token & user details
}
}
Also, I am using functional components, so It would be great if your solution is compatible with that. Looking forward to talking, thanks in advance!
you should create action to send data to the server
and use the action in your function
const handleSubmit = async (e: FormEvent) => {
e.preventDefault();
dispatch(sendDataForSignup(e))
};
On submit fire an action with form data
import { signUp } from "actions";
const handleSubmit = async (e) => {
e.preventDefault();
signUp(formData);
};
which will forward the type and payload to watcher
export const signUp = (payload) => ({
type: "SIGNUP_REQUEST",
payload
});
the watcher will take the latest action and pass it to worker function that will make the api call and dispatch an action with response type and data
import { put, takeLatest, all } from "redux-saga/effects";
function* signupWorker() {
const json = yield fetch("URL/signup").then((response) => response.json());
yield put({ type: "SIGNUP_SUCCESS", json: json.data });
}
function* signupWatcher() {
yield takeLatest("SIGNUP_REQUEST", signupWorker);
}
export default function* rootSaga() {
yield all([signupWatcher()]);
}
and the reducer will handle the data as you like based on the types
const reducer = (state = {}, action) => {
switch (action.type) {
case "SIGNUP_REQUEST":
return { ...state, loading: true };
case "SIGNUP_SUCCESS":
return { ...state, ...action.payload, loading: false };
default:
return state;
}
};
Related
Hey guys I am a beginner and trying to learn redux-saga with redux-toolkit. I am getting an empty user object and when I consoled the axios response, it is showing all the data, but not in handler.
this is the code.
UserInfoSlice
import {createSlice} from '#reduxjs/toolkit';
export const userInfoSlice = createSlice({
name: 'user',
initialState: {
user: {},
},
reducers: {
getUser() {},
setUser(state, action) {
const userInfo = action.payload;
console.log(userInfo)
return {...state, ...userInfo};
}
},
})
export const {getUser, setUser} = userInfoSlice.actions;
export const selectUserInfo = state => state.user.user;
export default userInfoSlice.reducer;
store
import {configureStore, combineReducers, getDefaultMiddleware} from '#reduxjs/toolkit';
import createSagaMiddleware from "redux-saga";
import {watcherSaga} from '../saga/RootSaga';
import userinfoReducer from '../features/UserInfoSlice';
const sagaMiddleware = createSagaMiddleware();
const reducers = combineReducers({
user: userinfoReducer,
});
const store = configureStore({
reducer: reducers,
middleware: [...getDefaultMiddleware({thunk: false}), sagaMiddleware]
});
sagaMiddleware.run(watcherSaga);
export default store;
rootsaga
import { takeLatest } from "redux-saga/effects";
import {handlegetUserInfo} from './handlers/UserInfo';
import {getUser} from '../features/UserInfoSlice';
export function* watcherSaga() {
yield takeLatest(getUser.type, handlegetUserInfo)
}
userInfoHandler
import { call, put } from "redux-saga/effects";
import { requestGetUserInfo } from '../requests/UserInforequest';
import { setUser } from "../../features/UserInfoSlice";
export function* handlegetUserInfo() {
try {
const response = yield call(requestGetUserInfo);
console.log(response)
const { data } = response;
console.log(data);
yield put(setUser({ ...data }))
} catch(e) {
console.log(e)
}
}
userinforequest
import axiosInstance from '../../../axios';
export function* requestGetUserInfo() {
return axiosInstance.get('api/user/')
}
please let me know what I have done wrong here and how to resolve this.?
Thank you
Generator vs. Async
You are defining requestGetUserInfo as a generator function with function*. This means that you need to yield the result.
export function* requestGetUserInfo() {
return yield axios.get('api/user/');
}
Alternatively, you can define it as an async function and await the result.
export async function requestGetUserInfo() {
return await axios.get('api/user/');
}
Slice Shape
Depending on the shape of your API response, there might be an issue in your setUser case reducer.
The initialState of your userInfoSlice declares that this is an object with a property user. Your setUser reducer sets the properties from the action.payload userInfo as top-level properties of the state. This would only be ok your API returns an object with a property user. More likely, it returns the user object and you'd want to set this userInfo to the user property of your state.
setUser(state, action) {
const userInfo = action.payload;
console.log("payload", userInfo);
return { ...state, user: userInfo };
}
Code Sandbox Demo
end developer and recently I started to learn front-end. I have troubles with adding some new data to redux store. I am working with dandelion-pro react template and can't figure out how to add my reducers to their stores, it seems much more complex then redux stores I have build for other projects, also I observed they used redux saga. I am trying to introduce a global state for user data on login.
Here is code for my reducer
import { CallToAction } from '#material-ui/icons';
import { SUCCESSFUL_LOGIN, FETCH_LOGIN, ERROR_LOGIN } from '../../actions/actionConstants';
const initialState = {
auth: false,
isLoading: false,
errMess: null,
isAdmin: false,
token: ''
}
export default function userReducer (state = initialState, action) {
console.log("Action: ")
console.log(action)
switch (action.type) {
case SUCCESSFUL_LOGIN: return {
...state,
auth: true,
isLoading: false,
errMess: null,
isAdmin: action.payload.isAdmin,
token: action.payload.token
}
case FETCH_LOGIN: return {
...state,
auth: false,
isLoading: true,
errMess: null
}
case ERROR_LOGIN: return {
...state,
auth: false,
isLoading: false,
errMess: action.payload
}
default: return state
}
}
Code for fetch user data
import { SUCCESSFUL_LOGIN, FETCH_LOGIN, ERROR_LOGIN } from '../../actions/actionConstants';
import axios from 'axios';
import { server } from '../../config'
export const fetchUser = (username, password) => (dispatch) => {
console.log("a ajuns")
dispatch(loginLoading(true));
axios.post(`${server + "/auth/login"}`, { username, password })
.then(res => {
const user = res.data;
console.log(user);
if (user.status) {
window.location.href = '/app';
return dispatch(loginUser(user));
}
else {
var errmess = new Error("False Status of User");
throw errmess;
}
})
.catch(error => dispatch(loginFailed(error.message)))
}
export const loginLoading = () => ({
type: FETCH_LOGIN
});
export const loginFailed = (errmess) => {
return ({
type: ERROR_LOGIN,
payload: errmess
})
};
export const loginUser = (user) => ({
type: SUCCESSFUL_LOGIN,
payload: user
})
Section that combine reducers
/**
* Combine all reducers in this file and export the combined reducers.
*/
import { reducer as form } from 'redux-form/immutable';
import { combineReducers } from 'redux-immutable';
import { connectRouter } from 'connected-react-router/immutable';
import history from 'utils/history';
import languageProviderReducer from 'containers/LanguageProvider/reducer';
import login from './modules/login';
import uiReducer from './modules/ui';
import initval from './modules/initForm';
import user from '../my_redux/modules/initForm';
/**
* Creates the main reducer with the dynamically injected ones
*/
export default function createReducer(injectedReducers = {}) {
const rootReducer = combineReducers({
user,
form,
login,
ui: uiReducer,
initval,
language: languageProviderReducer,
router: connectRouter(history),
...injectedReducers,
});
// Wrap the root reducer and return a new root reducer with router state
const mergeWithRouterState = connectRouter(history);
return mergeWithRouterState(rootReducer);
}
I try to connect my Login component like this
const mapStateToProps = state => ({
user: state.user
});
const mapDispatchToProps = dispatch => ({
fetchUser: (username, password) => dispatch(fetchUser(username, password))
});
// const mapDispatchToProps = dispatch => ({
// actions: bindActionCreators(userActions, dispatch),
// });
export default withStyles(styles)(connect(mapStateToProps, mapDispatchToProps)(Login));
The store is created here
/**
* Create the store with dynamic reducers
*/
import { createStore, applyMiddleware, compose } from 'redux';
import { routerMiddleware } from 'connected-react-router';
import { fromJS } from 'immutable';
import createSagaMiddleware from 'redux-saga';
import createReducer from './reducers';
export default function configureStore(initialState = {}, history) {
let composeEnhancers = compose;
const reduxSagaMonitorOptions = {};
// If Redux Dev Tools and Saga Dev Tools Extensions are installed, enable them
/* istanbul ignore next */
if (process.env.NODE_ENV !== 'production' && typeof window === 'object') {
/* eslint-disable no-underscore-dangle */
if (window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({});
// NOTE: Uncomment the code below to restore support for Redux Saga
// Dev Tools once it supports redux-saga version 1.x.x
// if (window.__SAGA_MONITOR_EXTENSION__)
// reduxSagaMonitorOptions = {
// sagaMonitor: window.__SAGA_MONITOR_EXTENSION__,
// };
/* eslint-enable */
}
const sagaMiddleware = createSagaMiddleware(reduxSagaMonitorOptions);
// Create the store with two middlewares
// 1. sagaMiddleware: Makes redux-sagas work
// 2. routerMiddleware: Syncs the location/URL path to the state
const middlewares = [sagaMiddleware, routerMiddleware(history)];
const enhancers = [applyMiddleware(...middlewares)];
const store = createStore(
createReducer(),
fromJS(initialState),
composeEnhancers(...enhancers),
);
// Extensions
store.runSaga = sagaMiddleware.run;
store.injectedReducers = {}; // Reducer registry
store.injectedSagas = {}; // Saga registry
// Make reducers hot reloadable, see http://mxs.is/googmo
/* istanbul ignore next */
if (module.hot) {
module.hot.accept('./reducers', () => {
store.replaceReducer(createReducer(store.injectedReducers));
});
}
return store;
}
on login form submit I call this.props.fetchUser("admin", "admin"); but I get the following error:
Uncaught Error: Actions must be plain objects. Use custom middleware for async actions.
at dispatch (redux.js:198)
at eval (middleware.js:29)
at eval (redux-saga-core.dev.cjs.js:1412)
at Object.fetchUser (Login.js?f3c5:66)
at Login.submitForm (Login.js?f3c5:30)
at onSubmit (Login.js?f3c5:49)
at executeSubmit (handleSubmit.js?e3b3:39)
at handleSubmit (handleSubmit.js?e3b3:131)
at Form._this.submit (createReduxForm.js?d100:362)
at HTMLUnknownElement.callCallback (react-dom.development.js:149)
I reviewed my answer, and update it according to your question update
The syntax you use for defining async function is called a thunk a fancy name for a function that return a promise (or async function), anyway to use that pattern in code you need a library called redux-thunk
To apply the redux-thunk middle ware for your application,
npm install redux-thunk
then apply the middleware in your app store
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';
// Note: this API requires redux#>=3.1.0
const store = createStore(rootReducer, applyMiddleware(thunk));
example from official repo of redux-thunk
and for your code just add the thunk imported from redux-thunk in middleware array
import thunk from 'redux-thunk';
const middlewares = [sagaMiddleware, routerMiddleware(history), thunk];
Now for Saga
you need to have a root saga that run others sagas, and run the root saga from the created saga middleware
here're the steps:
1- create saga middleware(just like how you did, but we need to run the root saga from there too)
import createSagaMiddleware from 'redux-saga'
const sagaMiddleware = createSagaMiddleware();
// after you've created the store then run the root saga
sagaMiddleware.run(rootSagas);
2- create your rootSaga
export function* rootSagas() {
try {
yield fork(fetchUsersSaga);
} catch (error) {
console.warn(error);
}
}
3- create your fetch user saga
import { take, put, call } from "redux-saga/effects";
export function* fetchUsersSaga() {
while (true) {
const action: FetchUser = yield take(FETCH_USER);
try {
const response = yield call(usersService.fetchUsersOfProject, { ...paramsPassedToFetchUserFunction })
if (response) {
const { data: { response: { user } } } = response;
yield put(setUser({ user }));
}
} catch (error) {
yield put(fetchUser());
}
}
}
now you need to notice the big difference between saga and thunk, for thunk you write an action that is hard coded to do one thing(or multiple but it still for a more specific case) and in saga you listen for what ever action the store has dispatched and react to that action in generator code style
I saw this question has been asked a lot. But I failed to solve my issue. Forgive me for my incompetence. Trying redux first time. I am trying to do a login, logout operation.
Store configuration:
import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';
const initialState = {};
const middleware = [thunk];
const store = createStore(
rootReducer,
initialState,
composeWithDevTools(applyMiddleware(...middleware)),
);
export default store;
Reducer Index:
import { combineReducers } from 'redux';
import loginReducer from './loginReducer';
export default combineReducers({
loginData: loginReducer,
});
Login Reducer:
import { ERROR_LOG, LOGIN, LOGOUT } from '#/actions/types';
const initialState = {
token: null,
data: null,
error: null,
};
export default (state = initialState, action) => {
switch (action.type) {
case LOGIN:
return {
...state,
data: action.payload,
token:action.payload.token
};
case LOGOUT:
console.log('logout',action)
return{
...state,
data:null,
}
case ERROR_LOG:
return { ...state, error: action.payload };
default:
return state;
}
};
Action:
import axios from 'axios';
import { ERROR_LOG, LOGIN, LOGOUT } from './types';
import common from '../models/common';
export const loginAction = (data) => async (dispatch) => {
try {
const res = await axios.post(`${common.api}/sign-in`, data);
const loginData = await res.data;
dispatch({
type: LOGIN,
payload: loginData,
});
} catch (error) {
dispatch({
type: ERROR_LOG,
payload: error.response.data,
});
}
};
export const logOutAction = () => dispatch => {
console.log("inside action")
try {
console.log("inside try")
dispatch({
type: LOGOUT,
});
} catch (error) {
console.log("inside error")
dispatch({
type: ERROR_LOG,
payload: error.response.data,
});
}
};
Types are imported from another file.
export const ERROR_LOG = 'ERROR_LOG';
export const LOGIN = 'LOGIN';
export const LOGOUT = 'LOGOUT';
So on login button I am invoking the logicAction which is working fine. Still providing the code:
const handleSubmit = (values) => {
loginAction(values);
};
import and the react-redux connect used as docs said. Same way I tried to add logout to logout button.
onMenuClick = (event) => {
this.props.logOutAction();
}
But the same thing throwing me
Error: Actions must be plain objects. Use custom middleware for async
actions.
From console I can see it throws error immediately as it hits the dispatch . For that no console.log is printed.
I have absolutely no idea what is the problem here.
EDIT:
mapStateToProps in login component where it is working fine:
const mapStateToProps = (state) => ({
loginData: state.loginData,
});
export default connect(mapStateToProps, { loginAction })(Login);
mapStateToProps in header component:
const mapStateToProps = (state) => ({
loginData: state.loginData,
});
export default connect(mapStateToProps, { logOutAction })(AvatarDropdown);
Basically I just copy/paste from login component.
Try to remove the initialState from your store configuration. I don't think you need it and it might be the source of your error. Try to use this:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';
const middleware = [thunk];
const store = createStore(
rootReducer,
composeWithDevTools(applyMiddleware(...middleware)),
);
export default store;
I was using a framework which has built in store. So there was a conflict between stores.
The problem is:
I'm trying to use redux-saga in my react app, but i still has this error: Actions must be plain objects. Use custom middleware for async actions. Code it seems correct but no idea why gives that error. I'll be glad for all the help. I'm fighting with it for about two days and still doesn't have a solution. I tried to look up, but I still have this error.
action...
import { GET_DISTRICTS} from '../../constants';
const getAdres = async (url) => {
let response = await fetch(url);
let data = await response.json();
let list = [];
data.AdresList.Adresler.Adres.forEach((item) => {
console.info(item);
list.push({
label: item.ADI,
value: item.ID
});
});
return list;
};
export const actions = {
handleGetDistrictsData: async () => {
let districts = await getAdres(`url is here`);
return {
type: GET_DISTRICTS,
payload: districts
};
},
reducer...
import { GET_DISTRICTS } from '../../constants';
export const initialState = {
districts: [],
quarters: [],
streets: [],
doors: [],
districtSelected: false,
districtSelectedID: null,
quarterSelected: false,
quarterSelectedID: null,
streetSelected: false,
streetSelectedID: null,
doorSelected: false,
doorSelectedID: null
};
export default (state = initialState, action) => {
switch (action.type) {
case GET_DISTRICTS:
return {
...state,
districts: action.payload
};
default:
return state;
}
};
component...
import React, { useEffect, useState } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { actions as addressActions } from '../../../../redux/actions/address';
import Select from 'react-select';
const Districts = (props) => {
let [ fetchedData, setFetchedData ] = useState(false);
useEffect(() => {
props.handleGetDistrictsData();
setFetchedData(true);
});
return (
<React.Fragment>
<Select
name='adresSelect'
options={props.address.districts}
onChange={props.handleDistrictChange}
placeholder='Please Select'
/>
</React.Fragment>
);
};
const mapStateToProps = (state) => ({
address: state.address
});
const mapDispatchToProps = function(dispatch) {
return bindActionCreators({ ...addressActions }, dispatch);
};
export default connect(mapStateToProps, mapDispatchToProps)(Districts);
-------------
import React from 'react';
import Districts from './Districts';
const AddressSearchWidget = (props) => {
return (
<React.Fragment>
<Districts />
</React.Fragment>
);
};
export default AddressSearchWidget
store...
import { applyMiddleware, combineReducers, compose, createStore } from 'redux';
import createSagaMiddleware from 'redux-saga';
import rootSaga from './sagas/index';
import * as reducers from './';
export function initStore() {
const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const rootReducer = combineReducers(reducers);
const sagaMiddleware = createSagaMiddleware();
const store = createStore(rootReducer, composeEnhancer(applyMiddleware(sagaMiddleware)));
// Run sagas
sagaMiddleware.run(rootSaga);
return store;
}
handleGetDistrictsData returns a promise (all async functions return promises). You cannot dispatch a promise in plain redux saga, and redux-saga does not change this. Instead, dispatch a normal action, and have that action run a saga. The saga can then do async things, and when it's done dispatch another action. The reducer listens only for that second action.
// Actions:
export const getDistrictsData = () => ({
type: GET_DISTRICTS,
})
export const districtsDataSuccess = (districts) => ({
type: DISTRICTS_DATA_SUCCESS,
payload: districts
})
// Sagas:
export function* watchGetDistricts () {
takeEvery(GET_DISTRICTS, getDistricts);
}
function* getDistricts() {
let response = yield fetch(url);
let data = yield response.json();
let list = [];
data.AdresList.Adresler.Adres.forEach((item) => {
console.info(item);
list.push({
label: item.ADI,
value: item.ID
});
});
yield put(districtsDataSuccess(list));
}
// reducer:
export default (state = initialState, action) => {
switch (action.type) {
case DISTRICTS_DATA_SUCCESS:
return {
...state,
districts: action.payload
};
default:
return state;
}
};
I am making a React-redux component to embed in Laravel blade file. Where in the react side,
I am using redux with the thunk, When I try to get data without thunk from the Laravel route, it getting properly.
But When I use an axios request in the action creator to get data asynchronously. It gives the:
'Uncaught Error: Actions must be plain objects. Use custom middleware for async actions.'
This is the entry component of the react side.
Entry.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import reducer from '../store/reducers/reducer';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import thunk from "redux-thunk";
const store = createStore(reducer, applyMiddleware(thunk)
+window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
// console.log(getState());
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>
, document.getElementById('like_post'));
This is the App.js main component
import React, { Component } from "react";
import { connect } from "react-redux";
import PropTypes from 'prop-types';
import axios from 'axios';
import {getCurrentPostLikes} from '../store/actions/actions';
class App extends Component {
constructor(props) {
super(props)
var domain = window.location.hostname;
var url = window.location.pathname;
var urlsplit = url.split("/");
var post_slug = urlsplit[urlsplit.length - 1];
this.state={
post_slug: post_slug,
post_likes:''
}
}
kFormatter(num) {
return num > 999 ? (num / 1000).toFixed(1) + 'k' : num
}
componentDidMount() {
this.props.getCurrentPostLikes();
// axios.get(`/api/get_post_likes/${this.state.post_slug}`)
// .then(response => {
// console.log(response.data.likes);
// this.setState({
// post_likes: response.data.likes
// })
// })
}
render() {
return (
<div className="App">
<img src="/images/love.svg" alt="post like" width="50px" height="50px"/>
<p>{this.kFormatter(this.state.post_likes)}</p>
<p><span>{this.props.likes}</span></p>
</div>
);
}
}
export default connect(null, {getCurrentPostLikes})(App);
// export default connect( mapStateToProps, mapDispachToProps )(App);
This is the actions.js file /store/actions/actions.js
// types.js is also defined properly as
// export const GET_POST_LIKES = 'GET_POST_LIKES';
import axios from 'axios';
import {GET_POST_LIKES} from './types';
// Get current post likes
export const getCurrentPostLikes = () => dispatch => {
return dispatch => {
setTimeout(() => {
axios.get(`/api/get_post_likes/2`)
.then(res => {
// console.log(response.data.likes);
// console.log(getState());
setTimeout(() => {
dispatch({
type: GET_POST_LIKES,
payload: res.data.likes
})
}, 4000);
})
.catch(err => {
dispatch({
type: GET_POST_LIKES,
payload: {}
})
})
}, 3000);
}
}
Tried this action creator also, but still same error
export const getCurrentPostLikes = () => {
return dispatch => {
axios.get(`/api/get_post_likes/2`)
.then(res => {
dispatch({
type: GET_POST_LIKES,
payload: res.data.likes
})
})
.catch(err => {
dispatch({
type: GET_POST_LIKES,
payload: {}
})
})
}
}
This is the reducers.js file under /store/reducers/reducer.js
import { GET_POST_LIKES } from '../actions/types';
const initialState = {
likes: null
};
const reducer = (state=initialState, action) => {
const newState = {...state};
switch(action.type){
case 'GET_POST_LIKES':
return {
...state,
post: action.payload
}
case 'LIKE_UP':
newState.likes += action.value
break;
}
return newState;
};
export default reducer;
Now, This should return a field value of posts table, with post id = 2.
Your problem is at Entry.js, this line:
const store = createStore(reducer, applyMiddleware(thunk)
+window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
You are setting the thunk middleware as the second parameter. The second parameter is for the initial state.
The thunk middleware should be part of the composed enhancers in the third parameter of the createStore function.
The parameters should be applied as such:
createStore(
connectRouter(history)(rootReducer),
initialState,
composedEnhancers
)
Full example:
import { createStore, applyMiddleware, compose } from 'redux'
import { connectRouter, routerMiddleware } from 'connected-react-router'
import thunk from 'redux-thunk'
import createHistory from 'history/createHashHistory'
import rootReducer from './core/reducers'
export const history = createHistory()
const initialState = {}
const enhancers = []
const middleware = [thunk, routerMiddleware(history)]
if (process.env.NODE_ENV === 'development') {
const devToolsExtension = window.__REDUX_DEVTOOLS_EXTENSION__
if (typeof devToolsExtension === 'function') {
enhancers.push(devToolsExtension())
}
}
const composedEnhancers = compose(
applyMiddleware(...middleware),
...enhancers
)
export default createStore(
connectRouter(history)(rootReducer),
initialState,
composedEnhancers
)
This is the 'signature' of an action creator curried function using redux-thunk:
export const getCurrentPostLikes = () => async dispatch => {
const response = await axios.get(`/api/get_post_likes/2`);
dispatch({ type: GET_POST_LIKES, payload: response.data.likes });
};
This is how you want your action creator to look:
export const getCurrentPosts = () => {
return async function(dispatch, getState) {
const response = await axios.get("/api/get_post_likes");
dispatch({ type: "GET_POST_LIKES", payload: response });
};
};
Leave the Promises out of it and go with ES7 async/await syntax and also leave out the setTimeout, unless you can provide a really good justification for wanting to time out an asynchronous request that is already taking a non-zero amount of time to complete, maybe I don't understand, but you do want that data don't you? It's necessary for your reducers to do their jobs and eventually update state in your application.
What I have above is not perfect in that it's boilerplate, like you don't really need getState in there, yes it's part of middleware, but if you are not going to use something, no need to define it. Anyway I am trying to get you to a point where your action creator works again and you are sparing your eyes and your mind.
It seems your action has an extra dispatch =>
export const getCurrentPostLikes = () => dispatch => {
setTimeout(() => {
axios.get(`/api/get_post_likes/2`)
.then(res => {
// console.log(response.data.likes);
// console.log(getState());
setTimeout(() => {
dispatch({
type: GET_POST_LIKES,
payload: res.data.likes
})
}, 4000);
})
.catch(err => {
dispatch({
type: GET_POST_LIKES,
payload: {}
})
})
}, 3000);
}