ReduxSaga makes my website re-render infinite - redux

Im using Redux-Saga and Redux-Thunk, this is store I have configured, but it makes my website re-render infinitely. Can u tell me what should i do to solve that? Thank u.
Store.js
import { createStore, applyMiddleware, compose } from 'redux'
import createSagaMiddleware from '#redux-saga/core'
import EmployeeReducer from './reducers/EmployeeReducer'
import thunk from 'redux-thunk'
import axiosMiddleware from "redux-axios-middleware";
import HttpService from "app/services/HttpService";
import RootReducer from './reducers/RootReducer'
import {getEmployeeList} from './saga'
const initialState = {};
const sagaMiddleware = createSagaMiddleware()
const middlewares = [
thunk,
sagaMiddleware,
axiosMiddleware(HttpService.getAxiosClient())
];
export const Store = createStore(
RootReducer,
initialState,
compose(
applyMiddleware(...middlewares)
)
);
sagaMiddleware.run(getEmployeeList)
This is saga.js where i import getEmployeeList
import { call, cancel, put, takeEvery, takeLatest } from 'redux-saga/effects'
import axios from 'axios'
import { GET_EMPLOYEE_LIST } from './actions/EmployeeActions'
import ConstantList from '../../app/appConfig'
const API_PATH = ConstantList.API_ENPOINT + "/employees"
export function* getEmployeeList() {
yield takeEvery(GET_EMPLOYEE_LIST, workEmployeeList)
}
export function* workEmployeeList() {
console.trace("hello");
try {
const url = API_PATH + '/search'
const response = yield call(axios.post, url, {})
yield put({
type: GET_EMPLOYEE_LIST,
payload: response.data.data
})
} catch (error) {
console.log("Request failed!")
}
}

The issue is that you are using the same action type to start the saga & to store the results to redux store.
// here you are waiting for GET_EMPLOYEE_LIST to trigger the saga
yield takeEvery(GET_EMPLOYEE_LIST, workEmployeeList)
// here you are using the same action type to store
// response data to redux store
yield put({
type: GET_EMPLOYEE_LIST,
payload: response.data.data
})
Because of that every time you finish fetching data another saga is triggered.
What you want is to have another action type to store the data in redux store like FETCH_EMPLOYEE_LIST_SUCCESS. Don't forget to update your reducer condition to use the correct action type as well.

You built yourself an infinite loop here:
yield takeEvery(GET_EMPLOYEE_LIST, workEmployeeList)
this means: "every time, GET_EMPLOYEE_LIST is dispatched, execute workEmployeeList"
And in workEmployeeList you have:
yield put({
type: GET_EMPLOYEE_LIST,
payload: response.data.data
})
this means "dispatch GET_EMPLOYEE_LIST".
So 1. leads to 2. and 2. leads to 1.
I can't really suggest anything to fix this as this is missing a lot of building blocks to work in the first place.
But what I can tell you is that we as Redux maintainers really recommend against using sagas for tasks like data fetching.
If you want to know about the backgrounds, I would recommend you watch the evolution of Redux Async Logic.
But generally, seeing you are just getting into Redux, my advice would be to really take a step back and go through our official Redux Tutorial first.
Not only shows it modern (post-2019) Redux which is about 1/4 of the code you are writing here and a lot less confusing, but it also shows multiple different approaches to data fetching.

Related

Is it okay to use Redux Toolkit and Redux Saga together?

is it ok to use the Redux Toolkit, even if I only create Slice in it and solve middleware via Redux Saga?
Or the best practice, in this case, is to use Redux Saga + raw Redux without Toolkit?
Thanks
Redux-saga is just a middleware that handles async logic better. If you have a large-scale app, redux-saga is preferred over redux-thunk. redux-saga makes the testing easier.
the difference between Redux and Redux toolkit is redux toolkit requires less code to set up and redux-toolkit uses immer.js to update the state easier.
To integrate redux-saga with redux-toolkit, you write your sagas and then combine them
import { all } from "redux-saga/effects";
import { firstSaga } from "./firstSaga";
export default function* rootSaga() {
yield all([...firstSaga]);
}
and then in store.js
import { configureStore } from "#reduxjs/toolkit";
import createSagaMiddleware from "redux-saga";
import rootSaga from "./rootSaga";
const sagaMiddleware = createSagaMiddleware();
const store = configureStore({
reducer: {
movie: MovieReducer,
},
middleware: (getDefaultMiddleware) =>
// adding the saga middleware here
getDefaultMiddleware().concat(sagaMiddleware),
});
sagaMiddleware.run(rootSaga);
export default store;

Redux-Toolkit Middleware custom: Uncaught RangeError: Maximum call stack size exceeded

I am studying Redux-Toolkit studio, and in particular, I am approaching custom middleware.
Below I report the definition of my store within my classic application example:
import { combineReducers, configureStore} from '#reduxjs/toolkit';
import {todosSlice} from '.. /features/todos/todosSlice';
import { filterStore } from '.. /features/todos/filterSlice';
import { myLog } from '.. /reduxtoolkitmiddlewarecustom/myLog';
const rootReducer = combineReducers({
todos: todosSlice.reducer,
filter: filterStore.reducer
});
export const store = configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) => getDefaultMiddleware(). concat(myLog),
});
As you see in the store amount, the middleware "custom" defined in '.. /reduxtoolkitmiddlewarecustom/myLog';
The definition of middleware is as follows:
export const myLog = (store) => {
console.log('Init myLog');
return function myDispatch(next) {
return function myAction(action) { //action is the action received from the store.
store.Dispatch({ type: 'INIT_MYLOG', payload: null });
console.log(action);
//return next(action);
}
}
};
Well, if I emit a Dispatch, I do not return next(action), is that the return I still get an error of "Uncaught Rangeerror: Maximum call stack size exceeded".
I thought I understood, but evidently, I don't. The only way to make the code work is, for example, to make a console.log
What am I tripping about?
If found the error, in general, if the middleware makes a Dispatch in the store, must a corresponding reducer/action creator exist?
Thank you so much
your middleware dispatches INIT_MYLOG on every action. and that dispatched INIT_MYLOG then triggers your middleware. and that then dispatches. an endless circle.
Solution? Don't dispatch in a middleware except if you do it as a reaction to one specific action.

React-redux dispatch action onclick using hooks and redux toolkit

Fairly new to redux, react-redux, and redux toolkit, but not new to React, though I am shaky on hooks. I am attempting to dispatch an action from the click of a button, which will update the store with the clicked button's value. I have searched for how to do this high and low, but now I am suspecting I am thinking about the problem in React, without understanding typical redux patterns, because what I expect to be possible is just not done in the examples I have found. What should I be doing instead? The onclick does seem to capture the selection, but it is not being passed to the action. My goal is to show a dynamic list of buttons from data collected from an axios get call to a list of routes. Once a button is clicked, there should be a separate call to an api for data specific to that clicked button's route. Here is an example of what I currently have set up:
reducersRoutes.js
import { createSlice } from "#reduxjs/toolkit";
import { routesApiCallBegan } from "./createActionRoutes";
const slice = createSlice({
name: "routes",
initialState: {
selected: ''
},
{... some more reducers...}
routeSelected: (routes, action) => {
routes.selected = action.payload;
}
},
});
export default slice.reducer;
const { routeSelected } = slice.actions;
const url = '';
export const loadroutes = () => (dispatch) => {
return dispatch(
routesApiCallBegan({
url,
{...}
selected: routeSelected.type,
})
);
};
createActionRoutes.js
import { createAction } from "#reduxjs/toolkit";
{...some other actions...}
export const routeSelected = createAction("routeSelection");
components/routes.js:
import { useDispatch, useSelector } from "react-redux";
import { loadroutes } from "../store/reducersRoutes";
import { useEffect } from "react";
import { routeSelected } from "../store/createActionRoutes";
import Generic from "./generic";
const Routes = () => {
const dispatch = useDispatch();
const routes = useSelector((state) => state.list);
const selected = useSelector((state) => state.selected);
useEffect(() => {
dispatch(loadroutes());
}, [dispatch]);
const sendRouteSelection = (selection) => {
dispatch(routeSelected(selection))
}
return (
<div>
<h1>Available Information:</h1>
<ul>
{routes.map((route, index) => (
<button key={route[index]} className="routeNav" onClick={() => sendRouteSelection(route[0])}>{route[1]}</button>
))}
</ul>
{selected !== '' ? <Generic /> : <span>Data should go here...</span>}
</div>
);
};
export default Routes;
Would be happy to provide additional code if required, thanks!
ETA: To clarify the problem - when the button is clicked, the action is not dispatched and the value does not appear to be passed to the action, even. I would like the selection value on the button to become the routeSelected state value, and then make an api call using the routeSelected value. For the purpose of this question, just getting the action dispatched would be plenty help!
After writing that last comment, I may actually see a couple potential issues:
First, you're currently defining two different action types named routeSelected:
One is in the routes slice, generated by the key routeSelected
The other is in createActionRoutes.js, generated by the call to createAction("routeSelection").
You're importing the second one into the component and dispatching it. However, that is a different action type string name than the one from the slice - it's just 'routeSelection', whereas the one in the slice file is 'routes/routeSelected'. Because of that, the reducer logic in the slice file will never run in response to that action.
I don't think you want to have that separate createAction() call at all. Do export const { routeSelected } = slice.actions in the slice file, and dispatch that action in the component.
I'm also a little concerned about the loadroutes thunk that you have there. I see that you might have omitted some code from the middle, so I don't know all of what it's doing, but it doesn't look like it's actually dispatching actions when the fetched data is retrieved.
I'd recommend looking into using RTK's createAsyncThunk API to generate and dispatch actions as part of data fetching - see Redux Essentials, Part 5: Async Logic and Data Fetching for examples of that.

Confusion with how createStore works in redux

I was learning Redux and came across createStore function. So, as I understood createStore receives 3 parameters:
reducer
initial state
enhancers (for simplicity we will use only middlewares)
But when we use createStore in action we do not pass initial state as the second argument BUT pass reducer with default state like this:
const initialState = {counter:0}
const reducer =(state=initialState, action)=>...
The question is why don't we put the initial state as the second argument but pass initialState to reducer?
I think you are confusing the initial state of a reducer to that of the global state of your app.
Global state simply means that combined state of all the reducers in your app.
For simplicity let's just assume you only have one reducer in your app.
Reducer :
function todos(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return state.concat([action.text])
default:
return state
}
}
So this simple function todos is our reducer which will give us the current state tree, whenever it is run.
So this is our first parameter to createStore.
Initial State :
['Understanding Store']
Let's assume our initial state as an array which contains 1 item as shown above.
This will be our second parameter to createStore.
Now we create our store like this:
import { createStore } from 'redux'
//... code
.
.
.
const store = createStore(todos, ['Understanding Store'])
Now our store is created.
Nothing fancy, store is basically an object, which has few methods on it.
One of those methods is dispatch.
This method helps in dispatching an action, which will run through our reducer and then update the state.
So when we do this
store.dispatch({
type: 'ADD_TODO',
text: 'Learn methods on Store'
})
This will update our state as below:
['Understanding Store','Learn methods on Store']
But when your app grows big, you might want to create different functions (reducers) to manage different parts of your global state.
If we have one more reducer, say counter.js :
export default function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
Then to combine our 1st reducer todos and this counter reducer, we have an utility called combineReducer.
rootReducer.js
import { combineReducers } from 'redux'
import todos from './todos'
import counter from './counter'
export default combineReducers({
todos,
counter
})
Then using createStore, you just do this:
import { createStore } from 'redux'
import rootReducer from './rootReducer.js;
const store = createStore(rootReducer);
There are certain rules that you need to follow while using combineReducers.
Read the rules here
The use case for passing an initial state as the second argument in createStore is intended for use cases where you get this initial state from the outside when loading your app. Examples could be state generated on the server for server-side rendered applications that are hydrated on the client, or an application that restores the redux state form local-storage when loaded.
The initial value of a single reducer should be returned when the reducer function is called with undefined state, the easiest way is to use a default argument for state:
const reducer = (state = initialState, action) => ...
This allows you to define the initialState close to where the reducer is defined and it scales nicely with combineReducers when you have a larger number of reducers. If you would put all the initialState of all reducers into one object that is passed to createStore this would become difficult to keep in sync.
I think you have actually confused createStore with a reducer. Think of createStore as a function which returns you a collection of reducers by adding in a certain middleWares and other functionalities like dispatch
More often than not you have multiple reducers in your app and you actually combine them using combineReducers
Say for example you combineReducers is
import userReducer from 'reducers/user';
import authReducer from 'reducers/auth';
import countReducer from 'reducers/count';
const reducers = combineReducers({
userReducer,
authReducer,
countReducer,
});
Now the initialState to createStore must be of the format of an object with keys as userReducer, authReducer, countReducer and then the individual reducers state. For example
{
userReducer: { id: 1, name: 'Test'},
authReducer: { isLoading: true, isAuthenticated: false},
countReducer: {counter: 0}
}
Now thing of the second keys as equivalent to initialState each individual reducer
For example: reducer/count.js
const initialState = {counter:0}
const reducer =(state=initialState, action)=>...
The way it works is that createStore would actually call the reducer with the action each time action is invoked like
reducer(state, action);
In case of combineReducer it works like below
const combineReducers = (reducers) => {
return (state, action) => {
const tempState = { ...state };
Object.keys(reducers).forEach((key) => {
tempState[key] = reducers[key](tempState[key], action);
});
return tempState;
};
};
and for the initial time it invokes it with
reducer(initialState, {type: "##redux/INIT"});
so that each reducer's initial state is populated
P.S. If you do not pass the initialState to createStore, each reducer takes in the default argument passed to to it like const reducer =(state=initialState, action)=> and returns state for default switch clause causing the initialState from each reducer to be used

What is the correct way to combine redux-thunk and redux-batched-actions?

What is the correct way to plug redux-batched-actions into my existing Redux store? I am completely confused by the Redux middleware API.
Currently I am using redux-thunk and redux-little-router.
Here is the code source that creates my Redux store:
import { createStore, applyMiddleware, compose, combineReducers } from 'redux'
import thunk from 'redux-thunk'
import { routerForBrowser } from 'redux-little-router'
import reducers from './store'
import routes from './routes'
const { reducer, middleware, enhancer } = routerForBrowser({ routes })
// Combine all reducers and instantiate the app-wide store instance
const allReducers = combineReducers({ ...reducers, router: reducer })
// Build middleware (if devTools is installed, connect to it)
const allEnhancers = (window.__REDUX_DEVTOOLS_EXTENSION__
? compose(
enhancer,
applyMiddleware(thunk, middleware),
window.__REDUX_DEVTOOLS_EXTENSION__())
: compose(
enhancer,
applyMiddleware(thunk, middleware)))
// Instantiate the app-wide store instance
const initialState = window.initialReduxState
const store = createStore(
allReducers,
initialState,
allEnhancers
)
The redux-batched-actions documentation exposes two usages: enableBatching and batchDispatchMiddleware. Which one should I use in my case?
Answering my own question after the return of my expedition into the fabulous source code of redux, redux-thunk, redux-batched-actions, ...
The correct way to do it seems to be using batchDispatchMiddleware, like this:
import { batchDispatchMiddleware } from 'redux-batched-actions'
// ...
const allEnhancers = (window.__REDUX_DEVTOOLS_EXTENSION__
? compose(
enhancer,
applyMiddleware(batchDispatchMiddleware, thunk, middleware),
window.__REDUX_DEVTOOLS_EXTENSION__())
: compose(
enhancer,
applyMiddleware(batchDispatchMiddleware, thunk, middleware)))
Note: I don't know if I could dispatch batched thunks, though. I don't do that in my current application. Use at your own risk!

Resources