Reset state of a Redux store causing incosistent selectors with React Router - redux

I'm experiencing a number of issues trying to reset my state of Redux when changing routes.
On enter in routeA and click browser history to routeB, after firing ##router/LOCATION_CHANGE for routeB it performs the selectors of wrong routeA and causes various undefined state errors.
It should not run the routeA selector, as i'm getting into routeB and I believe this is the problem but I have no idea how to solve it.
In some even happened to be showing duplicate keys in the state, sometimes by the way I'm trying to reset the state wrong.
My scenery
In onEnter of the React Router i execute replaceReducers, see:
My routes onEnter callback:
function onEnter() {
replaceReducers({ book, dragAndDrop })
}
replaceReducers:
function replaceReducers(reducers) {
const appReducers = combineReducers({ ...reducers, ...defaultReducers })
store.replaceReducer((state, action) => {
if (action.type === 'RESET_STORE') {
const { routing, application } = state
state = { routing, application }
}
return appReducers(state, action)
})
store.dispatch({ type: 'RESET_STORE' })
apollo.setStore(store)
}
Steps I followed and what happened the error:
Enter routeA;
Go to routeB;
Click in browser history to routeA;
routeB is called incorrectly;
Error in console:
Uncaught TypeError: Cannot read property 'get' of undefined in routeB-selector.js
getItems: book => book.get('items')
This routeB selector can not be called and something has run it automatically.
Help me, please...

Related

NuxtJS store returning local storage values as undefined

I have a nuxt application. One of the components in it's mounted lifecycle hook is requesting a value from the state store, this value is retrieved from local storage. The values exist in local storage however the store returns it as undefined. If I render the values in the ui with {{value}}
they show. So it appears that in the moment that the code runs, the value is undefined.
index.js (store):
export const state = () => ({
token: process.browser ? localStorage.getItem("token") : undefined,
user_id: process.browser ? localStorage.getItem("user_id") : undefined,
...
Component.vue
mounted hook:
I'm using UserSerivce.getFromStorage to get the value directly from localStorage as otherwise this code block won't run. It's a temporary thing to illustrate the problem.
async mounted() {
// check again with new code.
if (UserService.getFromStorage("token")) {
console.log("user service found a token but what about store?")
console.log(this.$store.state.token, this.$store.state.user_id);
const values = await ["token", "user_id"].map(key => {return UserService.getFromStorage(key)});
console.log({values});
SocketService.trackSession(this, socket, "connect")
}
}
BeforeMount hook:
isLoggedIn just checks that the "token" property is set in the store state.
return !!this.$store.state.token
beforeMount () {
if (this.isLoggedIn) {
// This runs sometimes??? 80% of the time.
console.log("IS THIS CLAUSE RUNNING?");
}
}
video explanation: https://www.loom.com/share/541ed2f77d3f46eeb5c2436f761442f4
OP's app is quite big from what it looks, so finding the exact reason is kinda difficult.
Meanwhile, setting ssr: false fixed the errors.
It raised more, but they should probably be asked into another question nonetheless.

Watching for a redux-saga action in redux

All of my API calls are handled by redux-sagas. I'm creating a heartbeat modal in my app to detect inactivity. Each time a saga goes off I want to clear my setTimeout so I know that the user is active.
My middleware is a basic one at the moment:
const heartbeatMonitor => store => next => action {
if (action['##redux-saga/SAGA_ACTION']) {
clearTimeout(window.myTimeout);
}
window.myTimeout = window.setTimeout(function() {
// send off an action to tell user they are inactive
}, 100000);
}
It seems like looking for this symbol, ##redux-saga/SAGA_ACTION, is the only way to tell if the action is a saga. I see that redux-sagas has a createSagaMiddleware(options) and I tried using effectMiddlewares but it doesn't seem like you have access to the dispatch method in there so I can't send off a new actions.
but it doesn't seem like you have access to the dispatch method in there so I can't send off a new actions.
Not sure whether this is the kind of solution you wanted, but you do have access to the dispatch method where your comment // send off an action to tell user they are inactive is located in your code snippet, as it is exposed by the store object. (this is documented in the Store Methods Section of the store in the redux docs)
Therefore something like the following should satisfy your case:
const heartbeatMonitor => store => next => action {
if (action['##redux-saga/SAGA_ACTION']) {
clearTimeout(window.myTimeout);
}
const { dispatch } = store;
window.myTimeout = window.setTimeout(() => {
dispatch({ type: "USER_INACTIVE" });
}, 100000);
}
Note: I would probably implement this differently (using redux-sagas effects) Maybe this is an option for you too:
Example Saga
import { put, delay } from "redux-saga/effects";
function* inactiveSaga() {
yield delay(100000);
yield put({ type: "USER_INACTIVE" })
}
Example Integration of saga above:
(add the following in your root saga)
//import { takeLatest } from "redux-saga/effects";
takeLatest(() => true, inactiveSaga)
Explanation: Every action will trigger the inactiveSaga (cause () => true). The inactiveSaga will wait 100000ms before dispatching the "inactive action". If there is a new action within this waiting time the previous execution of the inactiveSaga will be canceled (cause takeLatest, see redux-saga effect docs for takeLatest) and started from the beginning again. (Therefore no "inactive action" will be sent and the inactiveSaga will start to wait for these 100000ms again, before being cancelled or completing the delay and dispatching the "inactive action")

ngrx/effects not dispatching mapped actions

I've got a pretty straight forward effect defined using the ngrx/effects library.
#Effect()
public Authorize$ = this._actions$.ofType(IdentityActionsService.AUTHORIZE_IDENTITY)
.switchMap(action => this._svc.Authorize$(action.payload))
.catch(err => Observable.of(null).do(() => console.error(err); }))
.map(identity => this._identity.OnIdentityAuthorized(identity))
The #Effect is triggered, authorize$() runs, and the OnIdentityAuthorized() method, which returns an Action ({type: payload: }) fires...
What I expect to happen is that the action returned by OnIdentityAuthorized() should get fed into the appropriate reducer - that is not happening.
I have a debugger call in OnIdentityAuthorized and in the corresponding reducer. The Action returned by OnIdentityAuthorized is not being dispatched. What might cause this? Am I misunderstanding something?
I feel like what I've got is basically identical to example 1 here: https://github.com/ngrx/effects/blob/master/docs/intro.md
EDIT
Added additional code sections... The effect triggers the OnIdentityAuthorized debugger statement, so the observable is emitting all the way through the async authorization call. The reducer case is not triggered...
Here is the OnIdentityAuthorized() implementation:
public static ON_IDENTITY_AUTHORIZED = '[IDENTITY] Authorized';
public OnIdentityAuthorized(identity: Identity | JWT): Action {
debugger;
return {
type: IdentityActionsService.ON_IDENTITY_AUTHORIZED,
payload: identity
};
}
Here is the reducer section:
switch (action.type) {
case IdentityActionsService.ON_IDENTITY_AUTHORIZED:
debugger;
return merge({}, action.payload);
Turns out to have been an issue with the way I was registering the reducers.
I was trying to do something exotic in order to add additional state endpoints for lazy loaded modules... turns out there is an issue there - but that is a different question.

Async Tests - Mocha and Chai - Ensure the done() callback is being called

I'm trying test my container component methods. My container had a async method that load all proposals and set in the state. Example.:
loadProposal(proposalId) {
return axios
.get("http://localhost:9292/api/proposal_drafts/1.json")
.then(response => {
this.setState({
proposal: Immutable.fromJS(response.data)
})
})
}
So, to test this method i get the component instance and call the method (the api url is mocked).
it("sets proposal in the state", (done) => {
const wrapper = shallow(<Container/>)
loadProposalRequest(1)
wrapper.instance().loadProposal(1).then(() => {
chai.expect(wrapper.state().proposal).to.be(Map)
done()
})
})
But i get this error from console:
Error: timeout of 2000ms exceeded. Ensure the done() callback is being
called in this test.
Ops: If i put a console.log(wrapper.state()) inside then() . The log shows my state correctly.
If chai.expect() throws an error (which I think is what's happening), two things will happen:
done won't get called, because of the thrown error;
the error won't get caught, because there's not additional error handling.
You should use Mocha's promise support instead to remove both issues:
it("sets proposal in the state", () => {
const wrapper = shallow(<Container/>)
loadProposalRequest(1)
return wrapper.instance().loadProposal(1).then(() => {
chai.expect(wrapper.state().proposal).to.be(Map)
})
})
You can also use chai-as-promised
you can write code that expresses what you really mean:
return doSomethingAsync().should.eventually.equal("foo");
or if you have a case where return is not preferable (e.g. style considerations) or not possible (e.g. the testing framework doesn't allow returning promises to signal asynchronous test completion), then you can use the following workaround (where done() is supplied by the test framework):
doSomethingAsync().should.eventually.equal("foo").notify(done);

React + MobX - not re-rendering update to state

I've setup a new sample/boilerplate project for testing out using Meteor with React & MobX (using Mantra architecture). The project is at https://github.com/markoshust/mantra-matui-mobx
I'm having an issue where the state change of the State.header.title property is not properly reflecting the updated state change on re-render.
My state is built by pulling in simple objects:
https://github.com/markoshust/mantra-matui-mobx/blob/master/client/modules/core/stores/route.js
Into one master observable object:
https://github.com/markoshust/mantra-matui-mobx/blob/master/client/main.js#L8
I'm listing for route change and calling an action to update state:
https://github.com/markoshust/mantra-matui-mobx/blob/master/client/modules/core/reactions/route.js#L10
This action updates state:
https://github.com/markoshust/mantra-matui-mobx/blob/master/client/modules/core/actions/route.js#L5
The console is logging out proper state change, so the state is being updated properly. However, the component is not being re-rendered with the updated state (this line is console.log'ing old state val):
https://github.com/markoshust/mantra-matui-mobx/blob/master/client/modules/core/containers/Header.js#L6
I'm seeing the 'updating...' message, so the component is re-rendering, but it appears to still be pulling in the old state. I did add observer to all of my react components:
https://github.com/markoshust/mantra-matui-mobx/blob/master/client/modules/core/components/Header.js
I needed to create a custom composer for MobX. I added a listen for autorun to re-compose the component.
https://github.com/markoshust/mantra-matui-mobx/blob/master/client/modules/core/libs/with_mobx.js
import { compose } from 'mantra-core';
import { autorun } from 'mobx';
export default function composeWithMobx(fn, L, E, options) {
const onPropsChange = (props, onData) => {
const reactiveFn = () => fn(props, onData);
autorun(reactiveFn);
return reactiveFn();
};
return compose(onPropsChange, L, E, options);
}

Resources