Issue with #ngrx/store and switch statements within reducers - ngrx

I have the following two #ngrx/store reducers:
import {ActionReducer, Action} from '#ngrx/store';
import {UserAccount} from '../shared/models/useraccount.model';
export const SET_CURRENT_USER_ACCOUNT = 'SET_CURRENT_USER_ACCOUNT';
export const UPDATE_CURRENT_USER_ACCOUNT_FIRST_NAME = 'UPDATE_CURRENT_USER_ACCOUNT_FIRST_NAME';
export const currentUserAccountReducer: ActionReducer<UserAccount> = (state: UserAccount, action: Action) => {
console.log('currentUserAccountReducer:', state, action);
switch (action.type) {
case SET_CURRENT_USER_ACCOUNT: {
return action.payload;
}
case UPDATE_CURRENT_USER_ACCOUNT_FIRST_NAME: {
state.firstName = action.payload;
return state;
}
}
};
export const SET_AUTHENTICATED = 'SET_AUTHENTICATED';
export const SET_UNAUTHENTICATED = 'SET_UNAUTHENTICATED';
export const authenticatedReducer: ActionReducer<boolean> = (state: boolean, action: Action) => {
console.log('authenticatedReducer:', state, action);
switch (action.type) {
case SET_AUTHENTICATED: {
return true;
}
case SET_UNAUTHENTICATED: {
return false;
}
}
};
However, for some reason when I issue a dispatch for the 1st reducer (i.e. currentUserAccountReducer) then it changes the state for the 2rd reducer (i.e. authenticatedReducer)...
Here is the dispatch causing this issue:
this.store.dispatch({type: SET_CURRENT_USER_ACCOUNT, payload: currentUserAccount});
Here is how I initialize the store in the imports section:
StoreModule.provideStore(
{
currentUserAccount: currentUserAccountReducer,
authenticated: authenticatedReducer
})
Can someone please provide advice?
edit: The issue is that authenticated ends up undefined!!

The switch statements in your reducers do not contain default cases. You need to add default cases that return the state, as the reducers will be called for all actions - the store has no way of knowing which reducer should be called for a particular action type, so each dispatched action is passed to every reducer.

Related

How to intercept when the action start and store is updated?

I am using NGRX. I have many actions and I want to know when actions start and when the store is updated.
The idea is to have a centralized way to get the information no matter what action is executed. I need to know when the store updates without subscribing to all selectors.
updateTitle ----> title is updated.
best,
Hmendez
You have to explicitly define actions
e.g.
import { createAction, props } from "#ngrx/store";
export enum HeaderActionTypes {
UpdateTitle = '[Title] Update Title',
UpdateTitleSuccess = '[Title] Update Title success'
}
export const UpdateTitle = createAction(HeaderActionTypes.UpdateTitle)
export const UpdateTitleSuccess = createAction(HeaderActionTypes.UpdateTitleSuccess, props<{ payload: string }>())
in reducer you can catch actions and update the state
e.g
import { createReducer, on } from "#ngrx/store";
export const initialState = {
... // Additional state properties can go here.
updatingTitle: false,
title: ''
}
export const reducer = createReducer(
initialState,
on(HeaderActionTypes.UpdateTitle, (state) => {
return {
...state,
updatingTitle: true
}
}),
on(HeaderActionTypes.UpdateTitleSuccess, (state, { payload }) => {
return {
...state,
updatingTitle: false,
title: payload
}
})
)
If title is getting updated with async call you have to add an Effect
Effect, Checkout the documentation.
Selectors, Use selectors to read state and bind it to UI.

why state.payload is undefined in reducer

why state is returned empty
i had tried many solutions from this website but non of them worked
const initialState = [];
export default function(state = initialState, actions) {
const { type, payload } = actions;
switch (type) {
case DASHBOARD:
return [...state,payload]
default:
return state;
}
}
when this state is mapped to props , payload become undefined.
here when i console log action.payload it is defined but could not be passed in return statement.
May I know for which case it failing?
For Dashboard case,it will be "DASHBOARD" until and unless it is not constant.
And for your default case,you need to change your initial state like this:
const initialState = {payload:"your initial payload",your_other_properties:"here"};
export default function(state = initialState, actions) {
const { type, payload } = actions;
switch (type) {
case "DASHBOARD":
return [...state,payload]
default:
return state;
}
}

redux state not changing

I'm using a Net Core, React-Redux boiler-plate, and when I run the fetch api action, the reducer state does not change at all.
Here is my action
import axios from "axios";
import config from '../config';
const ROOT_URL = config[process.env.NODE_ENV].api;
export const FETCH_EVENTS = "FETCH_EVENTS";
export function fetchEvents() {
const url = ROOT_URL +"/Event/GetAllEvents";
const request = axios.get(url);
return {
type: FETCH_EVENTS,
payload: request
};
}
my index reducer:
import { combineReducers} from 'redux';
import { routerReducer } from 'react-router-redux';
import dataReducer from './dataReducer'
const reducers = {
events: dataReducer
};
const rootReducer = combineReducers({
...reducers,
routing: routerReducer
});
export default rootReducer;
and my reducer:
import { FETCH_EVENTS } from "../actions/ExtractActions";
export default function (state = [], action) {
switch (action.type) {
case FETCH_EVENTS:
console.log("inside reducer")
return [action.payload, ...state];
}
return state;
}
So I add this code in the Home component:
function mapDispatchToProps(dispatch) {
return bindActionCreators({ fetchEvents }, dispatch);
}
function mapStateToProps(state) {
return {
events: state.events
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Home);
but when I try to run the action and try to see if the reducer state has changed, I get on console log an empty array for "this.props.events". Even though if I am trying to store api data to the state, I even tried modifying the reducer method and simply returning a string, but this.props.events returns an empty array [] again. I am guessing my redux is not working but I don't know why. I've been debugging all night long
componentWillMount() {
this.props.fetchEvents()
console.log(this.props.events)
}
I found the error. For some reason I had to call this.props.events in the render() method and not componentwillmount.
axios.get() is an async function. That's why you couldn't see the updated state when you logged it right after fetching the events. I would recommend you to use the redux-devtools-extension for debugging. Hope this helps. Cheers!

combine two redux reducers

I have a use case like this:
eventListReducer: will get a list of events based on date range
eventDetailReducer: will get the event details based on one event id
I know how to do the two above, my question:
When my page loads initially, I will get a list of events based on default date range and load the first event details, I can certainly create an
EventListAndDetailReducer to duplicate eventListReducer and eventDetailReducer. Is there any better way I can reuse the logic?
What I want to achieve is to have another action, that will first call getEvents and update the eventLists state, and then grab the first event and call setEvent and update the eventDetail state.
This is my eventDetailReducer:
const initialState = {
eventDetails: "",
}
const eventReducer = (state = initialState, action) => {
switch (action.type) {
case "SET_EVENT":
state = {
...state,
eventDetails: action.payload
};
break;
}
return state;
}
export default eventReducer;
This is my eventsReducer:
const initialState = {
eventsList: [],
}
//getEventsReducer
const getEventsReducer = (state = initialState, action) => {
switch (action.type) {
case "GET_EVENTS":
state = {
...state,
eventList: ["Joe", "Tom", "Marry"] //assuming this from some other endpoint
};
break;
}
return state;
}
export default getEventsReducer;
What about using EventListAndDetailReducer?
const initialState = {
eventsList: [],
eventDetails: ""
}
export function eventListAndDetailReducer(state, action) {
switch(action.type) {
case GET_EVENTS:
return {...state, eventList: eventsReducer(state.eventsList, action)}
case "SET_EVENT":
return {...state, eventDetails: eventDetailsReducer(state.eventDetails, action)}
default:
return state
}
}
and then somewhen start using combineReducers?
Why not just have the eventDetails reducer also update on the GET_EVENTS action?
const eventReducer = (state = initialState, action) => {
switch (action.type) {
case "SET_EVENT":
state = {
...state,
eventDetails: action.payload
};
break;
case "GET_EVENTS":
state = {
...state,
eventDetails: action.payload[0] // assuming payload is an array
};
break;
}
return state;
}
Remember, all reducers receive all actions, so it does not need to be a 1-1 mapping.
What I understand from you question is that you want another action to do both actions sequentially and be dependent on each. I assume you have some middle ware such as redux-thunk that allows actions to be more than plaIn functions!
export function combinedAction() {
return (dispatch, getState) => {
// Write fetch() request to get events list from anywhere.
// Following should be within .then() if you're using fetch.
// Here events are just hardcoded in reducer!
dispatch(return { type: GET_EVENTS, payload: events }).then( () => {
let event = getState().eventsList[0]
dispatch(return { type: SET_EVENT, payload: event })
})
};
}
This will fire up GET_EVENTS action first and it'll set events array in state.eventsList. Then next action just uses this state information to dispatch next action SET_EVENT. Refer here to learn about chaining actions. How to chain async actions?

Redux creates state with double nesting

I have a redux app that, amongst other things, updates a single string, hence the state could be reflected as:
{
theDataString: "someString",
otherData: { ...some other data... },
someListItems: [ ...a data array... ]
}
Hence I have the following reducer:
function updateDataString(state = {}, action) {
switch (action.type) {
case UPDATE_DATA_STRING:
return Object.assign({}, ...state, {theDataString: action.theDataString});
default:
return state;
}
}
However, when dispatch the UPDATE_DATA_STRING action, the theDataString value in the state becomes double nested:
{
theDataString: {theDataString: "someString"},
otherData: {... some other data... },
someListItems: [ ... a data array ... ]
}
This same problem has already been encountered here. However, the solution for them was that they were calling combineReducers on a single reducer when that was unnecessary. In my case, I'm calling combineReducers on multiple reducers - so their answer doesn't work for me. Also note that the same nesting problem does not occur for other data, only the top-level string gets double nested.
What is going wrong here?
EDIT:
I'm connecting the component that updates theDataString like this:
import {connect} from 'react-redux';
import {updateDataString} from './actions/actions';
import SomeList from './components/someList';
const mapStateToProps = (state) => {
return {someListItems: state.someListItems}
};
const mapDispatchToProps = (dispatch, placeHolder) => {
return {
updateDataString: (aString) => dispatch(updateDataSting(aString))
}
};
export default SomeListConnected = connect(mapStateToProps, mapDispatchToProps)(SomeList)
The action is set up as follows:
export const UPDATE_DATA_STRING = 'UPDATE_DATA_STRING';
export function updateDataString(aString) {
return {type: UPDATE_DATA_STRING, theDataString: aString}
}
EDIT2: Changing the way the reducer updates the state is a natural place to look for answers. However I have tried various permutations here with little effect:
Object.assign({}, ...state, {theDataString: action.theDataString});
Object.assign({}, state, {theDataString: action.theDataString});
{...state, {theDataString: action.theDataString}};
None of the above fix the problem.
Assuming that you've used combineReducers, your updateDataString reducer should be treating the state as a string, not an object:
function updateDataString(state = "", action) {
switch(action.type) {
case UPDATE_DATA_STRING: return action.newString;
default; return state;
}
}
The slice reducer will only see the string value as its "state", so you need to treat it that way.
See Using combineReducers for some further information on the topic.
Try this
return { ...state, theDataString: action.newString};
instead of this
return Object.assign({}, ...state, {theDataString: action.newString});
in your reducer.
I think you are using ... spread operator in the wrong way. And no need to use Object.assign() here.
Read more about spread operator here

Resources