I have created an example for fetching data from API where I used redux-thunk. The following code is working.
In this context, I want to rewrite my code but using redux saga.
import React from 'react';
import {createStore, applyMiddleware} from 'redux';
import ReactDOM from "react-dom";
import thunk from 'redux-thunk';
import axios from 'axios';
function App(props) {
const initialState = {
loading: false,
data: [],
error: ''
};
const reducer = function (state = initialState, action) {
switch (action.type) {
case 'START_FETCH':
return {
...state,
loading: true
};
case 'PROCESS_FETCH':
return {
...state,
loading: false,
data: action.payload,
error: ""
};
case 'END_FETCH':
return {
...state,
loading: false,
data: [],
error: action.payload
}
}
return state;
};
const START_FETCH = 'START_FETCH';
const PROCESS_FETCH = 'PROCESS_FETCH';
const END_FETCH = 'END_FETCH';
let startFetchFun = () => {
return {
type: START_FETCH,
loading: true
}
};
let processFetchFun = (users) => {
return {
type: PROCESS_FETCH,
payload: users
}
};
let endFetchFun = (error) => {
return {
type: PROCESS_FETCH,
payload: error
}
};
let fetchUsersWithThunk = () => {
return function (dispatch) {
dispatch(startFetchFun());
axios.get('https://jsonplaceholder.typicode.com/users')
.then((response) => {
dispatch(processFetchFun(response.data));
})
.catch((error) => {
dispatch(endFetchFun(error.message));
console.log(error.message);
})
}
};
const store = createStore(reducer, applyMiddleware(thunk));
store.subscribe(() => {
console.log(store.getState())
});
store.dispatch(fetchUsersWithThunk());
return (
<div className="main">
<h1>Redux-Thunk</h1>
</div>
);
}
ReactDOM.render(
<App/>, document.getElementById('root'));
I want to write the code above using redux saga, to understand better sagas. So, how to use redux-saga for this example? Who will be able to help me?
Redux Saga uses yield call to call promises like an api service and uses yield put to dispatch actions to the store.
The difference is about blocking and not blocking calls.
Because we want to wait for the server to respond our request we will use yield call that is a blocking function.
Instead of dispatching the action directly inside the generator saga uses yield put({ type: "actionName" }). That's also useful for testing purposese.
So you should wrote your saga as following:
import {all, fork, put, call, takeLatest} from 'redux-saga/effects';
function* handleRequest (action) {
try {
yield put(startFetchFunc()); // dispatch the action to the store.
const result = yiels call(apiService.users, [data to pass]); // wait for the response blocking the code execution.
yield put(processFetchFun(result)); // dispatch the action to the store containing the data
} catch (e) {
yield put(endFetchFun('Error'));
}
}
function* watchRequest() {
yield takeLatest({type: "START_FETCH"}, handleRequest);
}
export function* rootSaga() {
yield all([
fork(wathcRequest),
// ... more watchers will be here...
]);
}
Congfigure you store as explained here https://redux-saga.js.org/docs/introduction/BeginnerTutorial.html
I suggest you to read the documentation more than once. It contains a lot of useful information that at first might be strange but much clearer once you understand how it works.
You will need to configure your store to use saga middleware:
import React from 'react';
import createSagaMiddleware from 'redux-saga';
import { createStore, applyMiddleware } from 'redux';
import reducer from './reducers';
import rootSaga from './sagas';
const sagaMiddleware = createSagaMiddleware();
const store = createStore(
reducer,
applyMiddleware(sagaMiddleware, logger),
);
sagaMiddleware.run(rootSaga); // < -- rootSaga exports all sagas in your app
Then you can convert your thunk to a saga:
import {call} from 'redux-saga/effects';
function* fetchUsersSaga(payload){
try {
yield call(startFetchFun());
axios.get('https://jsonplaceholder.typicode.com/users')
.then((response) => {
yield call(processFetchFun(response.data));
})
} catch(err) {
yield call(endFetchFun(error.message));
console.log(error.message);
}
};
Related
I was trying to run my Redux app with redux-saga.
Basically on my store.js I have the following codes:
import { applyMiddleware, createStore } from "redux";
import createSagaMiddleware from "redux-saga";
import logger from "redux-logger";
import rootReducer from "./reducers/rootReducer";
import rootSaga from "./sagas/userSagas";
const sagaMiddleware = createSagaMiddleware();
const middleware = [sagaMiddleware];
if (process.env_NODE_ENV === "development") {
middleware.push(logger);
}
const store = createStore(rootReducer, applyMiddleware(...middleware));
sagaMiddleware.run(rootSaga);
export default store;
My usersApi.js looks something like this:
import axios from "axios";
export const loadUsersApi = async () => {
await axios.get("http://localhost:5000/users");
};
And here is my userSagas:
import * as type from "../actionType";
import {
take,
takeEvery,
takeLatest,
put,
all,
delay,
fork,
call,
} from "redux-saga/effects";
import { loadUsersSuccess, loadUsersError } from "../actions/userAction";
import { loadUsersApi } from "../services/userApi";
export function* onLoadUsersStartAsync() {
try {
const response = yield call(loadUsersApi);
if (response.status === 200) {
yield delay(500);
yield put(loadUsersSuccess(response.data));
}
} catch (error) {
yield put(loadUsersError(error.response.data));
}
}
export function* onLoadUsers() {
yield takeEvery(type.LOAD_USERS_START, onLoadUsersStartAsync);
}
const userSagas = [fork(onLoadUsers)];
export default function* rootSaga() {
yield all([...userSagas]);
}
When I run this on my HomePage.js file where I load and dispatch the data:
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { loadUsersStart } from "../redux/actions/userAction";
export default function HomePage() {
const dispatch = useDispatch();
useEffect(() => {
dispatch(loadUsersStart());
}, [dispatch]);
return (
<div>
<h1>Home</h1>
</div>
);
}
It gave me this error:
[HMR] Waiting for update signal from WDS...
index.js:1 TypeError: Cannot read properties of undefined (reading 'data')
at onLoadUsersStartAsync (userSagas.js:25)
at onLoadUsersStartAsync.next (<anonymous>)
at next (redux-saga-core.esm.js:1157)
at currCb (redux-saga-core.esm.js:1251)
index.js:1 The above error occurred in task onLoadUsersStartAsync
created by takeEvery(LOAD_USERS_START, onLoadUsersStartAsync)
created by onLoadUsers
created by rootSaga
Tasks cancelled due to error:
takeEvery(LOAD_USERS_START, onLoadUsersStartAsync)
I am not sure what's causing this error, but even the logger of my app doesn't even show the actions being dispatched.
Any idea how can I fix this issue?
You need to return promise from
export const loadUsersApi = () => {
return axios.get("http://localhost:5000/users");
};
Maybe next time try to use typescript. It will prevent You from similar mistakes
Alternatively, you can build your redux without using redux-saga.
This is how I usually set them up:-
A. Reducer
/slices/auth.js
import { createSlice } from '#reduxjs/toolkit'
const initialState = {
users: [],
loading: false,
success: false,
error: false,
message: ''
}
export const usersSlice = createSlice({
name: 'users',
initialState,
// // The `reducers` field lets us define reducers and generate associated actions
reducers: {
setUsers: (state, action) => {
// Redux Toolkit allows us to write "mutating" logic in reducers. It
// doesn't actually mutate the state because it uses the Immer library,
// which detects changes to a "draft state" and produces a brand new
// immutable state based off those changes
state.users = action.payload
},
setLoading: (state, action) => {
state.loading = action.payload
},
setSuccess: (state, action) => {
state.success = action.payload.status
state.message = action.payload.message
},
setError: (state, action) => {
state.error = action.payload.status
state.message = action.payload.message
}
}
})
export const { setUsers, setLoading, setSuccess, setError, setMessage } = usersSlice.actions;
// The function below is called a selector and allows us to select a value from
// the state. Selectors can also be defined inline where they're used instead of
// in the slice file. For example: `useSelector((state: RootState) => state.counter.value)`
export const selectUsers = (state) => state.users.users
export const selectLoading = (state) => state.users.loading
export const selectSuccess = (state) => state.users.success
export const selectError = (state) => state.users.error
export const selectMessage = (state) => state.users.message
export default usersSlice.reducer;
B. Store
store.js
import { configureStore, getDefaultMiddleware } from '#reduxjs/toolkit'
import usersReducer from '../slices/users'
export const store = configureStore({
reducer: {
users: usersReducer
},
middleware: getDefaultMiddleware({
serializableCheck: false
})
})
C. App Component
import { Provider } from 'react-redux'
import { store } from '../app/store'
export default function App() {
return {
<>
<Provider store={store}>
{/* your content... */}
</Provider>
</>
}
}
D. Component (where you use the redux)
import { useContext, useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { selectUsers, selectLoading, selectSuccess, selectError, selectMessage, setUsers, setLoading, setSuccess, setError } from '../slices/users'
import axios from 'axios'
export default function HomePage() {
const dispatch = useDispatch()
const users = useSelector(selectUser)
const loading = useSelector(selectLoading)
const success = useSelector(selectSuccess)
const error = useSelector(selectorError)
const message = useSelector(selectorMessage)
useEffect(() => {
async function init() {
dispatch(setLoading(true))
const response = await axios.get('http://localhost:5000/users')
if(response?.status == 200) {
dispatch(setUsers(response?.data?.data))
dispatch(setSuccess({ status: true, message: 'Successfully get users data.' }))
} else {
dispatch(setError({ status: true, message: 'Failed to get data.' }))
}
dispatch(setLoading(false))
}
return init()
}, [])
return {
<>
{user}
</>
}
}
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've implemented Redux-saga for the first time. I followed the documentation to create the saga.
The problem is Saga isn't triggering when the action type is dispatched. Let me elaborate a little.
I have a saga.js file that contains the watcher and worker saga in it. The root-saga.js just combines all the different sagas. The store.js file contains the basic setup code for the configuration for the sagas.
I'm dispatching the action type on a useEffect call in a component. The redux dev tool shows the action type is called correctly. Please have a look at the code.
The expected outcome is, the saga should automatically get called when the action type function is dispatched. The corresponding console logs should be shown.
Edit 1: Action definitions code are added
// store.js
import { createStore, applyMiddleware, compose } from 'redux';
import createSagaMiddleware from 'redux-saga';
import rootReducer from './reducers/root-reducer';
import rootSaga from './sagas/root-saga';
const sagaMiddleware = createSagaMiddleware();
const middlewares = [sagaMiddleware];
const store = createStore(
rootReducer,
compose(
window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__(),
applyMiddleware(...middlewares)
)
);
sagaMiddleware.run(rootSaga);
export default store;
// root-saga.js
import { all, call } from 'redux-saga/effects';
import { dashboardSagas } from './dashboard-sagas';
function* rootSaga() {
yield all([call(dashboardSagas)]);
}
export default rootSaga;
// saga.js page
import { takeLatest, put, call, all } from 'redux-saga/effects';
import axios from 'axios';
import { DASHBAORD } from '../types/dashboard';
import { DashboardSuccess, DashboardFail } from '../actions/dashboard';
import { APIS_ENDPOINTS } from '../../util/api-endpoints';
// watcher saga
export function* fetchDashboardSaga() {
yield takeLatest(DASHBAORD.START, fetchDashboardAsyncSaga);
}
// worker saga
function* fetchDashboardAsyncSaga() {
yield console.log('123');
try {
const resp = yield axios.get(APIS_ENDPOINTS.DASHBOARD);
yield console.log(resp);
yield put(DashboardSuccess(resp));
} catch (error) {
console.log(error);
yield put(DashboardFail('Something went wrong. Please try again'));
}
}
export function* dashboardSagas() {
yield all([call(fetchDashboardSaga)]);
}
// app.js
import { dashboardStart } from './redux/actions/dashboard';
function App() {
const dispatch = useDispatch();
useEffect(() => {
dispatch(dashboardStart());
}, [dispatch]);
return (
// some dom
);
}
// Action definition
import { DASHBAORD } from '../types/dashboard';
const dashboardStart = () => {
return {
type: DASHBAORD.START,
};
};
const DashboardSuccess = (param) => {
return {
type: DASHBAORD.SUCCESS,
payload: param,
};
};
const DashboardFail = (param) => {
return {
type: DASHBAORD.FAILED,
payload: param,
};
};
export { dashboardStart, DashboardSuccess, DashboardFail };
Thank you
I found the issue. It was some weird glitch with the compose function in the store.js for redux. I removed that and installed the redux-devtools-extension library. Working fine now.
// before
compose(
window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__(),
applyMiddleware(...middlewares)
)
// after
composeWithDevTools(applyMiddleware(...middlewares))
If the actions is dispatched properly then the problem lies in the registration of your sagas.
Try replacing this code block
function* rootSaga() {
yield all([call(dashboardSagas)]);
}
with:
function* rootSaga() {
yield all([
// manually call the sagas. Only watcher functions. example:
dashboardSagas.saga1(),
dashboardSagas.saga2()
]);
}
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);
}