Selector not fired without subscribe() - ngrx

I have two selectors:
isLoggedIn -
selectUserDetails
Which implementation looks like this:
export const selectAuthState = state => state.auth;
export const isLoggedIn = createSelector(
selectAuthState,
auth => auth.loggedIn
);
export const selectUserDetails = createSelector(
selectAuthState,
auth => auth.userDetails
);
Their state looks like this:
export interface State {
loggedIn: boolean,
userDetails: UserDetails
}
export const initialAuthState: State = {
loggedIn: false,
userDetails: undefined
};
I have a question about this line of code:
ngOnInit() {
this.isLoggedIn$ = this.store.pipe(
select(isLoggedIn)
)
this.userDetails$ = this.store.pipe(
select(selectUserDetails)
)
}
This part of code is fine:
this.isLoggedIn$ = this.store.pipe(
select(isLoggedIn)
)
but this:
this.userDetails$ = this.store.pipe(
select(selectUserDetails)
)
is only fired with subscription..
this.store.pipe(
select(selectUserDetails),
map(...rest of code)
).subscribe();
In other parts of code I also cannot use this selectUserDetails selector without subscribe().
At first I thought that it is a problem with undefined variable but even if user is logged in and userDetails is populated in global State it still is not fired.
Why?

Are you sure, you dont handle isLoggedIn in html file with async pipe?

Related

next-redux-wrapper: after hydration useSelector returns initial value (null), but getServerSideProps passes the correct value to the page

I got getServerSideProps like this, which gets token from cookie and gets uses it to fetch user data with RTKQ endpoint. Then dispatches that data to authSlice.
So far it's good.
const getServerSideProps = wrapper.getServerSideProps(
(store) =>
async ({ req, res }: GetServerSidePropsContext) => {
let result: AuthState = null;
const data = getCookie('device_access_token', { req, res });
if (data?.toString()) {
result = await store
.dispatch(
usersApi.endpoints.getUserByToken.initiate(data?.toString())
)
.unwrap();
}
if (result) store.dispatch(setUser(result));
return { props: { auth: result } };
}
);
Then I merge this auth data in the store like this:
const reducer = (state: ReturnType<typeof rootReducer>, action: AnyAction) => {
if (action.type === HYDRATE) {
console.log('payload#HYDRATE', action.payload);
const nextState = {
...state, // use previous state
...action.payload, // apply delta from hydration
};
if (state.auth.user) {
nextState.auth.user = state.auth.user;
nextState.auth.token = state.auth.token;
} // preserve auth value on client side navigation
return nextState;
} else {
return rootReducer(state, action);
}
};
console.log('payload#HYDRATE', action.payload); also shows correct data.
The problem is in a page where I export getServerSideProps,
const IndexPage: NextPage = ({ auth }: any) => {
console.log('user#index', auth);
console.log('userSelect#index', useSelector(selectCurrentUser));
return auth ? <Home /> : <NoAuthHome />;
};
auth shows correct value, but useSelector(selectCurrentUser) shows null
Can someone tell me if this is how it is intended to be, or I'm doing something wrong?
Because I don't want prop-drilling auth on countless pages, just use useSelector(selectCurrentUser) wherever necessary.
Finally found the problem!
problem was in _app.tsx
I wrapped <Component {...pageProps} /> with <Provider store={store} at the same time exporting with wrapper.withRedux(MyApp)

Is it possible to generate static pages in nextjs when using redux-saga?

Warning: You have opted-out of Automatic Static Optimization due to
getInitialProps in pages/_app. This does not opt-out pages with
getStaticProps
I tried different options, but I can’t achieve static page generation, even if I take out the functionality I need from getInitialProps from_app, then wrapping it in withRedux, I still get it in the end. I tried with this - https://github.com/kirill-konshin/next-redux-wrapper - but could not get the result, I assume that this is because of the redux-saga and the whole application will use getInitialProps
/store.js
const ReduxStore = (initialState /*, options */) => {
const sagaMiddleware = createSagaMiddleware();
const middleware = [sagaMiddleware];
const composeEnhancers =
typeof window === 'object' &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
// Specify extension’s options like name, actionsBlacklist, actionsCreators, serialize...
}) : compose;
const enhancer = composeEnhancers(
applyMiddleware(...middleware)
// other store enhancers if any
);
const store = createStore(
rootReducer,
initialState,
enhancer
);
store.runSaga = () => {
// Avoid running twice
if (store.saga) return;
store.saga = sagaMiddleware.run(saga);
};
store.stopSaga = async () => {
// Avoid running twice
if (!store.saga) return;
store.dispatch(END);
await store.saga.done;
store.saga = null;
// log('Stop Sagas');
};
store.execSagaTasks = async (ctx, tasks) => {
// run saga
await store.runSaga();
// dispatch saga tasks
tasks(store.dispatch);
// Stop running and wait for the tasks to be done
await store.stopSaga();
// Re-run on client side
if (!ctx.isServer) {
store.runSaga();
}
};
store.runSaga();
return store;
};
export default ReduxStore;
//_app.js
import { Provider } from 'react-redux';
import withRedux from 'next-redux-wrapper';
import App from 'next/app';
class MyApp extends App {
render() {
const {Component, pageProps, store} = this.props;
return <Provider store={store}>
<Component {...pageProps}/>
</Provider>;
}
}
export default withRedux(makeStore)(MyApp);
Has anyone experienced this or have any ideas? I will be grateful for any help

Redux testing: Actions must be plain objects. Use custom middleware for async actions

I have a Redux app and it is working perfectly without any errors. Now I am trying to test it with Enzyme, Jest and Sinon:
it('calls constructor', () => {
sinon.spy(SavedVariantsComponent.prototype, 'constructor')
const store = configureStore()(STATE1)
wrapper = mount(<SavedVariantsComponent store={store} match={{ params: {} }} />)
expect(SavedVariantsComponent.prototype.constructor).toHaveProperty('callCount', 1)
})
In SavedVariantsComponent I have mapDispatchToProps:
const mapDispatchToProps = (dispatch, ownProps) => {
return {
onSubmit: (updates) => {
dispatch(updateSavedVariantTable(updates))
const { match, analysisGroup } = ownProps
const { familyGuid, variantGuid, tagArray, gene } = match.params
const familyGuids = familyGuid ? [familyGuid] : (analysisGroup || {}).familyGuids
const combineVariants = /combined_variants/.test(match.url)
dispatch(loadSavedVariants(combineVariants, familyGuids, variantGuid, tagArray, gene))
},
loadSavedVariants: (...args) => dispatch(loadSavedVariants(...args)),
}
}
And loadSavedVariants look like that:
export const loadSavedVariants = (combineVariants, familyGuids, variantGuid, tagArray, gene = '') => {
return (dispatch, getState) => {
...
...
and the error while running jest is:
Actions must be plain objects. Use custom middleware for async actions.
Which makes an HTTP Request that may not work in the current case. How to fix this error? I need to test that the constructor was called, but later on will also need to see how the inner Components are rendered, so need to have mount there. I suppose I am doing something wrong in testing and not in the real code since the latter is working without any errors, warnings or issues.
You probably need to configure your mock store to work with redux-thunk. See: https://github.com/dmitry-zaets/redux-mock-store#asynchronous-actions
import configureStore from 'redux-mock-store'
import thunk from 'redux-thunk'
const middlewares = [thunk] // add your middlewares like `redux-thunk`
const mockStore = configureStore(middlewares)

can't unsubscribe on my ngrx selector call

I have the following code
selectProduct(id){
const sub = this.store$.select(ProductStoreSelector.selectProductByID(id)).subscribe((product) => {
this.store$.dispatch(new ProductStoreAction.SetSelectedProduct({productID: product.id}));
sub.unsubscribe();
});
}
Basically, I would like to get my list of product, and get one by ID, then change my store state so that the selectedProduct become the one I just selected
export const featureAdapter: EntityAdapter<IProduct> = createEntityAdapter<IProduct>({
selectId: model => model.id,
});
export const selectAllProducts: (state: object) => Array<IProduct> = featureAdapter.getSelectors(selectProductsState).selectAll;
export const selectProductByID = (id: string) => createSelector(
selectAllProducts,
(products) => products.find((product) => product.id === id)
);
and my store is an entityState of products with one selected
export interface State extends EntityState<IProduct> {
selectedProduct: IProduct;
}
but the problem is,
althougt I do get my productId back, I can't unsubscribe to sub.unsubscribe() because it is undefined.
You can use either take(1) to listen for values only one time. Otherwise try to unsubscribe like below:
selectProduct(id){
this.store$.select(ProductStoreSelector.selectProductByID(id)).subscribe((product) => {
this.store$.dispatch(new ProductStoreAction.SetSelectedProduct({productID: product.id}));
}).unsubscribe();
}
using take(1):
selectProduct(id){
this.store$.select(ProductStoreSelector.selectProductByID(id))
.take(1)
.subscribe((product) => {
this.store$.dispatch(new ProductStoreAction.SetSelectedProduct({productID: product.id}));
});
}

Redux-Thunk - how to wait for action creator to finish

I have this action creator:
type LoadOpenRequestsResult = ThunkAction<
Promise<void>,
IRootState,
undefined,
LoadOpenRequestsActions
>;
export const loadOpenRequests: ActionCreator<LoadOpenRequestsResult> = () => {
[...]
};
and I use it like that in my component:
public componentDidMount() {
this.props.loadOpenRequests();
}
And I connect my React component using the object version of mapDispatchToProps like this:
export default connect(
mapStateToProps,
{ loadOpenRequests }
)(MaintenanceOpenListScreen);
I’d like to do something when the async action is finished, something like this:
public componentDidMount() {
await this.props.loadOpenRequests();
doSomethingWhenThisAsyncIsDone();
}
but this.props.loadOpenRequests(); is not a Promise.
Does that mean I cannot use the object version of mapDispatchToProps?
I found the solution here: https://github.com/reduxjs/redux-thunk/issues/213#issuecomment-428380685
Basically, the answer is yes, you can't use the object version of mapDispatchToProps. You have to use the function version like so:
public componentDidMount() {
this.props.loadOpenRequests().then(() => doSomethingWhenThisAsyncIsDone());
}
[...]
const mapDispatchToProps = (
dispatch: ThunkDispatch<IRootState, undefined, Action>
) => ({
loadOpenRequests: () => dispatch(loadOpenRequests()),
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(MaintenanceOpenListScreen);

Resources