How to use NgRx in a composable way (without circular code dependencies on AppState, but still typesafe)? - ngrx

Problem
When applying NgRx to my Angular project, I want to avoid that any NgRx selectors or Angular components
depend on a global AppState type (that would be the effective generic type T of NgRx's Store<T>)
(because allowing that would lead to a circular dependency in the source code, somewhere between
the respective component and Angular's app.module.ts).
I struggle to achive that without compromising the TypeScript typechecker or the ESLint checker.
I am currently using NgRx 15 (with Angular 15) and the ESLint #ngrx/all-requiring-type-checking plugin.
Dissatisfying Workaround
I have a work-around, with some shortcommings.
This is derived from the current NgRx tutorial,
with following three code snippets:
my-counter.ngrx.ts defines the component' state and the NgRx action(s),
reducer(s) and selector(s):
import { createAction, createReducer, createSelector, on } from '#ngrx/store';
// component's state
export interface MyCounterState { count: number }
// action(s)
export const increment = createAction('[Counter Component] Increment');
export const decrement = createAction('[Counter Component] Decrement');
// reducer(s)
export const counterReducer = createReducer(
<MyCounterState>{ count: 0},
on(increment, (state: MyCounterState): MyCounterState => <MyCounterState>{...state, count: state.count + 1}),
on(decrement, (state: MyCounterState): MyCounterState => <MyCounterState>{...state, count: state.count - 1}),
);
// selector(s)
const selectMyCounterState = (state: {myCounterState: MyCounterState}) => state.myCounterState;
const projectCount = (state: MyCounterState): number => state.count;
export const selectCount = createSelector(selectMyCounterState, projectCount);
app.module.ts configures NgRx for the Angular application:
#NgModule({
imports: [
StoreModule.forRoot({ myCounterState: counterReducer }, {}),
...
],
...
})
export class AppModule { }
my-counter.component.ts uses the NgRx store:
#Component(...)
export class MyCounterComponent {
count$: Observable<number>;
constructor(private store: Store<{ myCounterState: MyCounterState }>) {
this.count$ = store.select(selectCount);
}
readonly increment = () => this.store.dispatch(increment());
readonly decrement = () => this.store.dispatch(decrement());
}
Open Problems
The workaround can be polished further, e.g. by declaring and using an
interface instead of { myCounterState: MyCounterState } and a constant
instead of { myCounterState: counterReducer }, but some problems remain:
the identifiers myCounterState in { myCounterState: MyCounterState }
(required for Selectors or other places accessing the Store) and myCounterState
in { myCounterState: counterReducer } have to match magically; the TypeScript typechecker cannot guarantee that at compile time (and likewise, refactoring tools cannot consistently rename them)
this seems to be a general problem of the NgRx API (because StoreModule.forRoot(...)
is configured by NgRx Reducers, and the type of the NgRx Store is derived implicitly
from the Reducer's type signatures)
the ESLint plugin #ngrx/all-requiring-type-checking still complains about the generic
parameter in constructor(private store: Store<{ myCounterState: MyCounterState }>) (in the component's code snippet above)
and wants to see a raw constructor(private store: Store) instead
which I find a strange demand from the ESLint plugin, because Store is a
generic type, and providing the type parameter is essential for the TypeScript
typechecker to check the Selector implementations and usages at compile time
Actual Questions
As shown above, I struggle to apply NgRx in a way that is composable and typesafe and accepted by NgRx ESLint plugin
(again, composable meaning that components and selectors should be independent of any global AppState type, the generic
type parameter of Store<T>).
Is there a concise and clean pattern for that?
Or is it simply impossible with the current NgRx 15 API?

As you mentioned, it's hard to type this correctly because the state in the store is "constantly" changing at run-time.
I think what you're missing in the snippets is a feature selector. In my opinion, using this makes interacting with the store simpler.
// instead of this
const selectMyCounterState = (state: {myCounterState: MyCounterState}) => state.myCounterState;
// do this
const selectMyCounterState = createFeatureSelector<MyCounterState>(myCounterStateKey);
While we can still make a mistake with this, it's a good base.
Because the selector to select the feature is now typed, you can use it further to create child selectors.
And you can also drop the generic from the Store. The selector will handle all of that.
Also, some newer features like createFeature help to keep things consistent and typed. You can also do Store.forFeature(myFeature), this way you don't need to share the interface in the module.
For example, a feature comes with the base selectors to retrieve the state.
Side note, you don't need to cast the reducer as this can lead to problems.
// don't do this
on(increment, (state: MyCounterState): MyCounterState => <MyCounterState>{...state, count: state.count + 1}),
// instead do
on(increment, (state: MyCounterState): MyCounterState => ({...state, count: state.count + 1})),
Some links that might be useful:
https://timdeschryver.dev/blog/you-should-take-advantage-of-the-improved-ngrx-apis
https://timdeschryver.dev/blog/stop-misusing-typescript-type-assertions

'Improved Workaround' Snippets
Thanks to #timdeschryver's answer, I can get rid of the ESLint warning and can get away with exporting less implementation details from my-counter.ngrx.ts. This solves most of the problems I had.
Still unresolved is the necessity for the magic match of the Reducer key (object field name) and the FeatureSelector key (string value), but at least these can be non-public implementation details that can be kept close together in one source file.
For sake of completness, this is the pattern I am now working with, based on the NgRx tutorial, with following three code snippets:
my-counter.ngrx.ts defines the component' state and the NgRx action(s),
reducer(s) and selector(s):
import { createAction, createFeatureSelector, createReducer, createSelector, on } from '#ngrx/store';
// component's state
interface MyCounterState { count: number }
// action(s)
export const increment = createAction('[Counter Component] Increment');
export const decrement = createAction('[Counter Component] Decrement');
// reducer(s)
const counterReducer = createReducer(
{ count: 0},
on(increment, (state: MyCounterState): MyCounterState => ({...state, count: state.count + 1})),
on(decrement, (state: MyCounterState): MyCounterState => ({...state, count: state.count - 1})),
);
export const MY_COUNTER_APP_REDUCERS = { myCounterState: counterReducer };
// selector(s)
const selectMyCounterState = createFeatureSelector<MyCounterState>('myCounterState');
const projectCount = (state: MyCounterState): number => state.count;
export const selectCount = createSelector(selectMyCounterState, projectCount);
app.module.ts configures NgRx for the Angular application:
#NgModule({
imports: [
StoreModule.forRoot({
...MY_COUNTER_APP_REDUCERS,
// ...and more
}),
...
],
...
})
export class AppModule { }
my-counter.component.ts uses the NgRx store:
#Component(...)
export class MyCounterComponent {
count$: Observable<number>;
constructor(private store: Store) {
this.count$ = store.select(selectCount);
}
readonly increment = () => this.store.dispatch(increment());
readonly decrement = () => this.store.dispatch(decrement());
}

Related

slice, action and reducers, what are they?

I'm trying to learn Redux, and i encountered this code:
reducers: {
loginStart: (state) => {
//...
},
loginSuccess: (state, action) => {
//...
},
loginFailure: (state) => {
//...
},
logout: (state) => {
//...
},
},
});
export const { loginStart, loginSuccess, loginFailure, logout } = userSlice.actions;
export default userSlice.reducer;
I can't understand well what are .actions, Slice, .reducer or reducers from different web sources.
So kindly can any expert in Redux here explain in a simplified way what are theses and their roles?
Every state of your app (which is global) lives in an object tree stored in a single store.
Actions are simply JavaScript objects that have a type with a payload of data illustrating exactly what is happening. what they do? they are the only way to manage our state tree. pay attention: no state has been mutated so far.
Reducers are just responses to our corresponding called action to perform on our immutable state and thus returning a new state. optionally you might also want to check Array.reduce() method to better understand reducers.
What is slice then? as it comes with redux-toolkit, slice contains all the reducer logics and actions for a single feature.
it auto generates your action creators and types which you have to define them as constants before redux-toolkit. check createSlice for the full explanation.
In your example the object called reducers goes into your createSlice with also an initial state and a name.
Based on all that being said, this is your final example of your question:
const initialState = {}
const authSlice = createSlice({
name: 'authentication',
initialState,
reducers: {
loginStart: (state) => {
//...
},
loginSuccess: (state, action) => {
//...
},
loginFailure: (state) => {
//...
},
logout: (state) => {
//...
},
},
})
export const { increment, decrement, incrementByAmount } = counterSlice.actions
export default counterSlice.reducer

Getting this error "Invariant failed: A state mutation was detected inside a dispatch, in the path: todoReducer.1."

I tried everything like spread operator but nothing works.
Here is my reducer
//state is an array of objects.
const initialState = [
{taskName: "kkkkk", isEdit: false},
]
export const todoReducer = (state=initialState, action) =>{
switch(action.type){
case 'add' :
const temp=
{
taskName: action.payload.taskName,
isEdit: action.payload.isEdit
}
state.push(temp);
return {state}
default: return state
}
}
The error message indicates that you are using Redux Toolkit - that is very good. The problem is that you are not using createSlice or createReducer and outside of those, in Redux you are never allowed to assign something to old state properties with = or call something like .push as it would modify the existing state.
Use createSlice instead:
const initialState = [
{taskName: "kkkkk", isEdit: false},
]
const slice = createSlice({
name: 'todos',
reducers: {
add(state, action) {
state.push(action.payload)
}
}
})
export const todoReducer = slice.reducer;
// this exports the auto-generated `add` action creator.
export const { add } = slice.actions;
Since the tutorial you are currently following seems to be incorporating both modern and completely outdated practices, I would highly recommend you to read the official Redux Tutorial instead, which shows modern concepts.

Problems with React Component Reusability with Redux Store as Global State

I am coming across a reusability conundrum in my React/Redux app. I have coded reusable React components, such as dropdowns and sliders, and I am storing the state values in my Redux Store. It is dawning on me now that anytime any JSX instance of the component changes state, all other instances of that component also change state. They all begin to mimic each other, whereas I need them to operate independently. I also want to initialize state differently in the different instances of the component, and can't figure out a way to do this. For example, for a slider, I want the beginning and ending value of the slider range to be different in different instances of the component. This would be easy to do through React's normal props system, but I am getting confused on how to do it via Redux.
I've researched this a bit and it seems the solution is to add unique IDs to each instance of the component in the Redux store. But I need specifics on where to add the ID (the reducer? the action creator? the component?) and how to have everything flow so that the right component is getting their state changed.
This is my reducer:
const INITIAL_STATE = {
slots: 100,
step: 5,
start: 35,
end: 55,
};
const sliderReducer = (state=INITIAL_STATE, action ) => {
switch (action.type) {
case "MIN_SELECTED":
console.log("Min Selected");
return {
...state,
start: action.payload
};
case "MAX_SELECTED":
console.log("Max Selected");
return {
...state,
end: action.payload
};
default:
return state;
}
}
export default sliderReducer;
this is my action creator:
//Slider Minimum Value Selection Action Creator
export const selectMinValue = (value) => {
return (dispatch) => {
dispatch({
type: "MIN_SELECTED",
payload: value
});
};
};
//Slider Maximum Value Selection Action Creator
export const selectMaxValue = (value) => {
return (dispatch) => {
dispatch({
type: "MAX_SELECTED",
payload: value
});
};
};
the component code is too long to paste, but it's event handlers for onDrag events and JSX for the slider essentially.
For anyone here who runs into the same problem, there are many libraries which attempt to solve this, in my opinion, in too complex a way. The cleanest and simplest way to resolve the issue is to associate a namespace identifier with each instance of the component in the redux store. The best answer I found is here: having multiple instance of same reusable redux react components on the same page/route
Good luck!

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.

Resources