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