How to add more than one extra argument to Redux Thunk - redux

The following code block describes how to add extra argument to redux thunk.
In my case I am applying dependency injection and I need to pass more than one argument to the Thunk, so is there any solutions other than gathering all arguments in just one object and pass it to the extraArgment thunk property.
import { configureStore } from '#reduxjs/toolkit'
import rootReducer from './reducer'
import { myCustomApiService } from './api'
const store = configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
thunk: {
extraArgument: myCustomApiService,
},
serializableCheck: false,
}),
})

No. That's literally the solution. You have only one extra. Of course you can make it an object and add all the things you need as properties. You could also pass a DI container in there or something. That's up to you.

Related

ReduxSaga makes my website re-render infinite

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.

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

using bindActionCreators, this.props.dispatch in react-redux disptach vs redux

I've read about bindActionCreators, i've compiled a resumen here:
import { addTodo,deleteTodo } from './actionCreators'
import { bindActionCreators } from 'redux'
function mapStateToProps(state) {
return { todos: state.todos }
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ addTodo, deleteTodo }, dispatch)
}
*short way
const mapDispatchToProps = {
addTodo,
deleteTodo
}
export default connect(mapStateToProps, mapDispatchToProps)(TodoApp)
another code use like this:
function mapDispatchToProps(dispatch) {
let actions = bindActionCreators({ getApplications });
return { ...actions, dispatch };
}
why previous code with bindActionCreators , don't need disptach parameter?
i've tried this way to get dispatch on this.props (but not working):
const mapDispatchToProps = (dispatch) => {
return bindActionCreators ({ appSubmitStart, appSubmitStop}, dispatch );
};
const withState = connect(
null ,
mapDispatchToProps,
)(withGraphqlandRouter);
why I had to change my old short way:
const withState = connect(
null ,
{ appSubmitStart, appSubmitStop}
)(withGraphqlandRouter);
in order to get this.props.dispatch()? because i neede to use dispatch for an isolated action creator inside a library with js functions. I mean before I don't needed use "bindActionCreators", reading this doc:
https://redux.js.org/api-reference/bindactioncreators
"The only use case for bindActionCreators is when you want to pass some action creators down to a component that isn't aware of Redux, and you don't want to pass dispatch or the Redux store to it."
I'm importing:
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
what is the difference using redux pure, and react-redux?
really I need "bindActionCreators" in my new code? because without this i can't see this.props.dispatch()
UPDATE:
I've found this solutions to get this.props.dispatch working:
const mapDispatchToProps = (dispatch) => {
return bindActionCreators ({ appSubmitStart, appSubmitStop, dispatch }, dispatch ); // to set this.props.dispatch
};
does anyone can explain me? how i can send same distpach like a creator ?
First let's clear our minds regarding some of the key concepts here:
bindActionCreators is a util provided by Redux. It wraps each action creators to a dispatch call so they may be invoked directly.
dispatch is a function of the Redux store. It is used to dispatch actions to store.
When you use the object shorthand for mapState, React-Redux wraps them with the store's dispatch using Redux's bindActionCreators.
connect is a function provided by React-Redux. It is used to connect your component to the Redux store. When you connect your component:
It injects dispatch to your component only if you do not provide your customized mapDispatchToProps parameter.
Regarding what happened above to your code:
Component will not receive dispatch with customized mapDispatchToProps
In the code here:
const mapDispatchToProps = (dispatch) => {
return bindActionCreators(
{ appSubmitStart, appSubmitStop, dispatch }, // a bit problematic here, explained later
dispatch
); // to set this.props.dispatch
};
You are providing your own mapDispatch, therefore your component will not receive dispatch. Instead, it will rely on your returned object to contain the action creators wrapped around by dispatch.
As you may feel it is easy to make mistake here. It is suggested that you use the object shorthand directly, feeding in all the action creators your component will need. React-Redux binds each one of those with dispatch for you, and do not give dispatch anymore. (See this issue for more discussion.)
Writing customized mapState and inject dispatch manually
However, if you do need dispatch specifically alongside other action dispatchers, you will need to define your mapDispatch this way:
const mapDispatchToProps = (dispatch) => {
return {
appSubmitStart: () => dispatch(appSubmitStart),
appSubmitStop: () => dispatch(appSubmitStop),
dispatch,
};
};
Using bindActionCreators
This is exactly what bindActionCreators does. Therefore, you can simplify a bit by using Redux's bindActionCreators:
const mapDispatchToProps = (dispatch) => {
return bindActionCreators(
{ appSubmitStart, appSubmitStop }, // do not include dispatch here
dispatch
);
};
As mentioned above, the problem to include dispatch in the first argument is that it essentially gets it wrapped around by dispatch. You will be calling dispatch(dispatch) when you call this.props.dispatch.
However, bindActionCreators does not return the object with dispatch. It's passed in for it to be called internally, it does not give it back to you. So you will need to include that by yourself:
const mapDispatchToProps = (dispatch) => {
return {
...bindActionCreators({appSubmitStart, appSubmitStop}, dispatch),
dispatch
};
};
Hope it helped! And please let me know if anything here is unclear :)
I have made some changes to your code please try this
import * as Actions from './actionCreators'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
const mapStateToProps = (state)=>(
{
todos: state.todos
}
)
const mapDispatchToProps = (dispatch)=> (
bindActionCreators(Actions, dispatch)
)
export default connect(mapStateToProps, mapDispatchToProps)(TodoApp)

update redux reducers after store initialization

I am new to redux architecture, I have this basic doubt can we update the reducers list after creating the store using combinedReducer and createStore methods?
Yes, you can update the reducers and inject a new one asynchronously with replaceReducer api of Redux store.
It is an advanced API. You might need this if your app implements code
splitting, and you want to load some of the reducers dynamically. You
might also need this if you implement a hot reloading mechanism for
Redux.
Take as example this starter-kit
In createStore.js file the reducers passed as arguments to the createStore method are the result of makeRootReducers(). Pay attention to the fact that no one async reducer have been passed to this function.
// extract of src/store/createStore.js
import { applyMiddleware, compose, createStore } from 'redux'
import { routerMiddleware } from 'react-router-redux'
import thunk from 'redux-thunk'
import makeRootReducer from './reducers'
export default (initialState = {}, history) => {
// ...
// ======================================================
// Store Instantiation and HMR Setup
// ======================================================
const store = createStore(
makeRootReducer(), // <------------- without arguments, it returns only the synchronously reducers
initialState,
compose(
applyMiddleware(...middleware),
...enhancers
)
)
store.asyncReducers = {}
// ...
}
In reducers.js file:
makeRootReducer function calls combineReducers with the default reducers
needed for the startup (like router reducer) and other "asynchronously" reducers passed as arguments
injectReducer is a function called for injecting new reducers on runtime. It call replaceReducer api on the store passing as argument a new list of reducers obtain through makeRootReducer(async) function
see below:
// src/store/reducers.js
import { combineReducers } from 'redux'
import { routerReducer as router } from 'react-router-redux'
export const makeRootReducer = (asyncReducers) => {
return combineReducers({
// Add sync reducers here
router,
...asyncReducers
})
}
export const injectReducer = (store, { key, reducer }) => {
store.asyncReducers[key] = reducer
store.replaceReducer(makeRootReducer(store.asyncReducers))
}
export default makeRootReducer
Finally, in the starter-kit the reducer is injected on route definition, like here:
// src/routes/Counter/index.js
import { injectReducer } from '../../store/reducers'
export default (store) => ({
path: 'counter',
/* Async getComponent is only invoked when route matches */
getComponent (nextState, cb) {
/* Webpack - use 'require.ensure' to create a split point
and embed an async module loader (jsonp) when bundling */
require.ensure([], (require) => {
/* Webpack - use require callback to define
dependencies for bundling */
const Counter = require('./containers/CounterContainer').default
const reducer = require('./modules/counter').default
/* ----> HERE <---- */
/* Add the reducer to the store on key 'counter' */
injectReducer(store, { key: 'counter', reducer }) // <-------
/* Return getComponent */
cb(null, Counter)
/* Webpack named bundle */
}, 'counter')
}
This technique is helpful when you want split a large app and avoid to load all the reducers at the boot.

ImmutableJS with Redux's combineReducers

Getting warning: "Using Maps as children is not yet fully supported" when combining reducers (each with its own immutable.js Map) with #redux combineReducers. What's a better store structure to avoid this?
import reducer_2 from './action_handlers/two';
import reducer_3 from './action_handlers/three';
import { combineReducers } from 'redux';
let reducer = combineReducers({
reducer_2,
reducer_3
});
export default reducer;
//=====================
// ./action_handlers/two.js
const INITIAL_STATE_2 = new Map({
foo: 'bar'
});
export default function reducer_2(state = INITIAL_STATE_2, action) {...}
//======================
// ./action_handlers/three.js
const INITIAL_STATE_3 = new Map({
baz: 'bat'
});
export default function reducer_3(state = INITIAL_STATE_3, action) {...}
//=======================
Looks like you’re using ES6’s Map instead of Immutable’s.
Write the following line of code in both your reducers:
import { Map } from 'immutable';
and then
const INITIAL_STATE_2 = Map({
foo: 'bar'
});
Or you can use
import Immutable from 'immutable';
const INITIAL_STATE_2 = Immutable.Map({
foo: 'bar'
});
It is not really clear whether you are relating your question to the Immutable.js Map data structure or to the regular Map object.
However, in both cases Redux's combineReducers creates a plain object as root-state. If you are using Immutable.js, there exists the redux-immutable package which provides a combineReducers function that returns an Immutable.js data structure instead of an object.
You can't user combine reducer of redux
a combineReducers function that comes with Redux will not work as it doesn’t know how to read and create new versions of state.

Resources