Observable from store.select doesn't seem to have updated values - ngrx

I have an ApplicationState which has project member and my ProjectState is something like this:
interface ProjectState {
listObjs: SomeType[];
}
In my component.ts onInit I have something like this:
...
this.objID = someString
...
this.listObj$ =
this.store.select( state => state.project.listObjs.find( item => item.id === this.objID));
...
In my reducer I am handling creation of new state like this:
...
case (action.ACTION1): {
const newState = {... state};
const objIndex = state.listObjs.findIndex( item => item.id === action.payload.id )
if ( objIndex === -1 ) {
return state;
}
Object.assign(newState.listObjs[objIndex].someMember, {... action.payload.someData});
return newState;
}
...
I subscribed to listObj$ in my component onInit, but it gets served when initializing and whenever I make changes to obj in array I don't see the subscribe getting updated data

This is "direct" state mutation.
The spread operator (...) does not clone recursive and Object.assign mutates newState.listObjs[objIndex].someMember.
You can install ngrx-store-freeze or enable the runtime checks in ngrx v8, and you'll get an error that you can't modify the state like this.
To solve this issue, you'll have to create a new state in an immutable way, or use immer.
Clean NgRx reducers using Immer

Related

Redux-Persist Library - Save data from counter to localStorage

Good day,
The beginnings of learning redux can be confusing
The steps in this code:
Create reducer for increment
Create Store
If user takes onClick action, function increment dispatch previously mentioned reducer
In this same component display data
Render component inside Provider with store
In this case need is saving the state with data to LocalStorage.
What should I do for save counter_state to local-storage?
// reducer
function counter(state=0, action) {
console.log('counter', action)
switch(action.type) {
case 'INCREMENT':
return state + 1;
return state;
}
}
///create store
const store = createStore(counter);
// React Component
class Counter extends React.Component {
increment() {
this.props.dispatch({
type: 'INCREMENT'
});
}
render() {
return (
<div>
{this.props.state}
<div>
<button onClick={this.increment.bind(this)}>+</button>
</div>
</div>
)
}
}
const mapStateToProps = function (state) {
return {state};
}
const CounterApp = connect(mapStateToProps)(Counter);
class Test5 extends React.Component {
render() {
return (
<Provider store={store}>
<CounterApp />
</Provider>
)
}
}
export default Test5;
localStorage has a pretty simple interface. I'd recommend looking up its API on mdn. Since you're not using any of the persistence libraries you're importing, might as well try it directly for learning purposes:
Write the value of state.counter to localStorage in your mapStateToProps
When you do createStore you can pass a second argument with preloadedState. So before you do that, read from localStorage to get the saved value of your counter.
Some remarks:
state in the reducer is usually an object with properties. It might work with a simple number, but if you want to learn redux you'll want to use an object here.
I'd recommend to move on to the more complicated libraries like redux-persist once you feel comfortable with the basics of redux. It's too much abstraction at once for getting a simple counter to work.

How to get previous state from Router_Cancel in ngrx?

I was trying to restrict the user from navigating away from the current page by using CanDeactivate (if form is dirty and not saved). By the time we click on any link, Router_Navigation event is getting called and it is updating the router state in store and if I cancel the page navigation on modal pop up (from can deactivate), Router_Cancel event is being called, but the current router state is not getting updated (it’s still pointing to other page).
I saw this in ngrx documentation:
ROUTER_CANCEL and ROUTER_ERROR contain the store state before the
navigation. Use the previous state to restore the consistency of the
store.
Can someone please help me on how to get previous state from Router_cancel Action.
Thanks
I solved this by creating an applicationRouter state to maintain current and previous routes, whenever ngrx router dispatches a ROUTER_NAVIGATION Event i am listening to it and updating my applicationRouterState.
At each point applicationRouter will have only two router events (current and previous state).
and whenever Router_Cancel is triggered i am toggling the previous router and current router state.
PFB, the soln:
#Effect()
navigationListener$ = this.actions$.ofType('ROUTER_NAVIGATION')
.pipe(
switchMap((routerNavigationAction: RouterNavigationAction<RouterDefinition>) => {
return of(routerNavigationAction).pipe(
withLatestFrom(this._routerNavigationData$),
map(([action, routerNavData]: [RouterNavigationAction<RouterDefinition>, RouterState]) => {
// TODO: Move this logic to Reducer
if (!(routerNavData.currentRouter && routerNavData.currentRouter.url
&& routerNavData.previousRouter && routerNavData.previousRouter.url)) {
routerNavData.previousRouter = routerNavData.currentRouter = action.payload.routerState;
} else {
routerNavData.previousRouter = routerNavData.currentRouter;
routerNavData.currentRouter = action.payload.routerState;
}
return new fromActions.MaintainPrevCurrRouterStateAction(routerNavData);
})
);
})
);
And this is my state object:
export interface RouterDefinition {
url: string;
queryParams: Params;
params: Params;
segments: string[];
}
export interface RouterState {
currentRouter: RouterDefinition;
previousRouter: RouterDefinition;
}
I use a ngrx/effect to store the latest two ROUTER_NAVIGATION actions, and re-dispatch the previous one when I get a ROUTER_CANCEL or ROUTER_ERROR so that the router state is completely restored.
#Injectable()
export class RouterEffects {
private previousRouterNavigationAction: RouterNavigationAction<
RouterStateUrl
>;
private currentRouterNavigationAction: RouterNavigationAction<
RouterStateUrl
>;
#Effect({ dispatch: false })
save$: Observable<Action> = this.actions$.pipe(
ofType(ROUTER_NAVIGATION),
switchMap((action: RouterNavigationAction<RouterStateUrl>) => {
this.previousRouterNavigationAction = this.currentRouterNavigationAction;
this.currentRouterNavigationAction = { ...action };
return Observable.empty();
})
);
#Effect()
load$: Observable<Action> = this.actions$.pipe(
ofType(ROUTER_CANCEL, ROUTER_ERROR),
switchMap(action => Observable.of(this.previousRouterNavigationAction))
);
constructor(private actions$: Actions) {}
}

Container Component not updating on action

I have the following reducer:
const selectEntityReducer = function(entityState, action) {
const selectedEntity = entityState.allEntities.filter(e => (e.id == action.id))[0]
const newStateForSelectEntity = updateObject(entityState, {selectedEntity: selectedEntity});
return newStateForSelectEntity;
};
entityState shape looks like {entities:[{id:1}, {id:2}], selectedEntity:{id:2}}.
I have a React component that is connected to the store like this:
function mapStateToProps(state) {
return {
selectEntity: state.entities.selectEntity
};
}
However, it never re-renders.
If I say selectEntities: state.entities in mapStateToProps, then the component re-renders. But I'm only interested in the selected entity for this component.
I'm aware that enforcing immutability down the entire state tree is required for redux to function properly. But I thought that is what I was doing in selectEntityReducer.
Any extra context can be seen in the full redux code in this gist.
Its happens because you filter old piece of state.
You must copy your entities:
const newEntities = entityState.allEntities.slice();
const newSelectedentities = newEntities.filter.........
Copy piece of ctate if it array and then process it.

Redux will execute all subscription callbacks every time an action is dispatched?

Gee, I feel foolish about this, but I have read every part of: http://redux.js.org/ (done the egghead tutorials, and read 4 times the FAQ at: http://redux.js.org/docs/faq/ImmutableData.html
What I did was stub one of my reducers, to always return state, and that is the only reducer being called (checked with breakpoints). Even so, my subscribe event is being called every time the reducer returns state. What Do I not understand? (Action.SetServerStats is being called at a 1Hz rate, and the subscribe is also being called at a 1Hz Rate
BTW the Chrome Redux Extension says thats states are equal, and the React Extension for Chrome with Trace React Updates, is not showing any updates.
I will be glad to remove the question, when someone clues me in. But right now, what I see each each of the reducers being called at 1Hz, and all of them returning the slice of the store that they got (state).
So do I not understand subscribe, and that it returns every time even when the store tree does not get modified (and it is up to react-redux to do shallow compare to figure out what changed if any?)
create store & subscribe
let store = createStore(reducer, initialState, composeWithDevTools(applyMiddleware(thunk)))
store.subscribe(() => console.log("current store: ", JSON.stringify(store.getState(), null, 4)))
reducers.js
import A from './actionTypes'
import { combineReducers } from 'redux'
export const GLVersion = (state = '', action) => {
switch (action.type) {
case A.SetGLVersion:
return action.payload
default:
return state
}
}
export const ServerConfig = (state = {}, action) => {
switch (action.type) {
case A.SetServerConfig: {
let { ServerPort, UserID, PortNumber, WWWUrl, SourcePath, FMEPath } = action.payload
let p = { ServerPort, UserID, PortNumber, WWWUrl, SourcePath, FMEPath }
return p
}
default:
return state
}
}
export const ServerStats = (state = {}, action) => {
switch (action.type) {
case A.SetServerStats:
return state
// let { WatsonInstalled, WatsonRunning, FMERunning, JobsDirSize } = action.payload
// let s = { WatsonInstalled, WatsonRunning, FMERunning, JobsDirSize }
// return s
default:
return state
}
}
export default combineReducers({ GLVersion, ServerConfig, ServerStats })
Correct. Redux will execute all subscription callbacks every time an action is dispatched, even if the state is not updated in any way. It is up to the subscription callbacks to then do something meaningful, such as calling getState() and checking to see if some specific part of the state has changed.
React-Redux is an example of that. Each instance of a connected component class is a separate subscriber to the store. Every time an action is dispatched, all of the wrapper components generated by connect will first check to see if the root state value has changed, and if so, run the mapStateToProps functions they were given to see if the output of mapState has changed at all. If that mapState output changes, then the wrapper component will re-render your "real" component.
You might want to read my blog post Practical Redux, Part 6: Connected Lists, Forms, and Performance, which discusses several important aspects related to Redux performance. My new post Idiomatic Redux: The Tao of Redux, Part 1 - Implementation and Intent also goes into detail on how several parts of Redux actually work.

Allow reducer to have access to state

I have a reducer that maintains the currently visible item from a list of some sort, with a case for displaying the next and previous item:
export function currentIndex(state = null, action) {
switch (action.type) {
case types.INCREMENT:
return state + 1
case types.DECREMENT:
return state - 1;
}
}
I also have a random state which is initially false but when set to true I want the currentListItem reducer to be able to account for this and output a a random number instead.
Which is the most idiomatic way of doing this in redux?
The idiomatic solution is to transfer your reducer logic into a thunk using a middleware package such redux-thunk (or similar).
This allows you to treat special kinds of actions as functions which means you can extend a plain action with specific action-related logic. The example you give of needing to access the state to conditionally determine the action logic is an excellent use-case for redux-thunk.
Below is a example of how you might pull the logic out of your reducer into a thunk. You should note that, unlike reducers, thunks explicitly support fetching state and dispatching subsequent actions via the getState and dispatch functions.
Thunk Example
export const increment= () => {
return (dispatch, getState) => {
const state = getState()
const delta = (state.random) ? getRandomNumber() : 1
dispatch({
type: INCREMENT,
delta
})
}
}
export function currentIndex(state = null, action) {
switch (action.type) {
case types.INCREMENT:
return state + action.delta
}
}

Resources