How to fix mapStateToProps from accessing an undefined state key without checking manually? - javascript

I am creating a single page web application with React, with the state stored using Redux. When I access something like state.workspace.guest.data in mapStateToProps() it causes an exception crashing the application.
It appears mapStateToProps() is invoked before componentDidMount(), which invokes this.props.getGuests() action that actually causes state.workspace.guest to be initialized. The page loads properly when I manually enter the URL and press enter. However, mapStateToProps() causes an exception when I navigate through the application, with state.workspace.guest being undefined.
A possible solution would be to check if state.workspace.guest is defined. However, I feel there is a better way to do it?
The answers in the following link suggest to use selectors. I am not sure if this is the solution to my problem, given I am new to Redux.
react js mapStateToProps triggers Uncaught TypeError: Cannot read property 'map' of undefined
Here are the mapping functions.
function mapDispatchToProps(dispatch) {
return bindActionCreators({
getGuests: Actions.getGuests
}, dispatch);
}
function mapStateToProps(state, ownProps) {
return {
guests : state.workspace.guest.data
}
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(GuestSearchTable));
The componentDidMount() function which actually triggers an action which in turn loads the data.
componentDidMount() {
this.props.getGuests();
}
Here is the reducer.
const initialState = {
data : []
};
function guestReducer(state = initialState, action) {
switch (action.type) {
case Actions.GET_GUESTS: {
return {
...state,
data : action.payload.data
};
}
case Actions.GET_GUEST: {
return {
...state,
guest: action.payload.data
};
}
case Actions.SAVE_GUEST: {
return {
...state,
guest: action.payload.data
};
}
default: {
return state;
}
}
};
I expect the initial value of the state.workspace.guest to be { data : [] }. But the actual value is undefined. The same problem appears in all the other pages.

You are trying to access data from guest key.
function mapStateToProps(state, ownProps) {
return {
guests : state.workspace.guest.data
}
}
I think you want to access state.workspace.data or you have to change key name to guests in reducer and the intialize state as follow:
return {
guests: state.workspace.guests
}

Related

Redux | Why this store's parameter type changes after second click?

I'm trying to push a new value in the store's state. It works fine the first time I click on the button "Add item", but the second time I got the following error: "state.basket.push is not a function". I configure the action to console log the state and got the following results:
1st click: {...}{basketItems: Array [ "44" ]}
2nd click: Object {basketItems: 0 }
Why the variable type is changing from array to an int?
Here is the code for the rendered component:
function Counter({ basketItems,additem }) {
return (
<div>
<button onClick={additem}>Add item</button>
</div>
);
}
const mapStateToProps = state => ({
basketItems: state.counterReducer.basketItems,
});
const mapDispatchToProps = dispatch => {
return {
additem: ()=>dispatch({type: actionType.ADDITEM, itemName:'Dummy text' }),
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(Counter);
And the reducer looks like this:
import {ADDITEM} from "../actions/types";
const initialState = { basket: [], };
export default function reducer(state = initialState, action) {
switch (action.type) {
case ADDITEM:
console.log(state);
// let newBasket = state.basket.push('44');
return {
...state,
basket: state.basket.push('44')
};
default:
return state;
}
}
I'm copying the state before updating the basket to prevent weird behaviors.
There's two problems here:
state.basket.push() mutates the existing state.basket array, which is not allowed in Redux
It also returns the new size of the array, not an actual array
So, you're not doing a correct immutable update, and you're returning a value that is not an array.
A correct immutable update here would look like:
return {
...state,
basket: state.basket.concat("44")
}
Having said that, you should really be using our official Redux Toolkit package, which will let you drastically simplify your reducer logic and catch mistakes like this.

Redux initial state disappears on action

I have an initial state in my reducer which is an empty object
const initialState = {
postHistory: {},
loading: true
};
I've noticed that when I run a function from a different component which dispatches the action that this initial state actually disappears. This is causing me problems because in one of my components I'm trying to detect the present of data in this object.
const obj = this.props.postHistory;
this.props.postHistory[Object.keys(obj)[0]] && this.props.postHistory[Object.keys(obj)[0]]
However when the action is fired, I can see in redux that the initial state for the object has completely disappeared and only the loading value is there.
TypeError: Cannot convert undefined or null to object
Has any one else encountered such an issue? Any advice to overcome welcome! Thank you.
Adding reducer code as suggested:
import { POST_HISTORY } from '../actions/types';
const initialState = {
postHistory: {},
loading: true,
};
export default function(state = initialState, action){
const { type, payload } = action;
switch(type){
case POST_HISTORY :
return {
...state,
postHistory: payload.data,
loading: false
}
default:
return state;
}
}
First console your payload.data....then check that the data which you are getting is in "JSON" format our string format..If the data is in "Object" form then it will store in your POSTHISTORY, and if your data is in "json" format then it will not store the data in initial state.

Reducer not returning updated state

I can't get my reducer to return updated state.
The action (confirmed with debugger) is an array of objects - something like: [{name: "test"}, {name: "second_test"}]
I think something must be wrong with my spread operator, although I've also tried Object.assign() in the debugger and that seems to return the expected result.
My components is apparently just getting the default state. Here's my reducer code. Any help would be appreciated. Thank you!
const initialState = {
current: {},
all: []
}
export default function deckReducer(state = initialState, action) {
switch(action.type) {
case 'CREATE_DECK':
return {...state, all: [...state.all, action.payload]}
case 'FETCH_DECKS':
debugger;
return {...state, all: action.payload}
default: return state
}
}
I had this very issue recently (exactly as you describe), and I resolved the problem by using the immutability-helper package which is listed here on the ReactJS docs.
This approach allows you to delegate the heavy lifting of state immutability and nested state updates. You should find this resolves your issue:
import update from 'immutability-helper';
const initialState = {
current: {},
all: []
}
export default function deckReducer(state = initialState, action) {
switch(action.type) {
case 'CREATE_DECK': {
/* Add payload to nested state.all list */
return update(state, { all : { $push : [action.payload] }});
}
case 'FETCH_DECKS': {
/* Replace state.all with incoming payload */
return update(state, { all : { $set : action.payload }});
}
default: return state
}
}

Getting error when setting the whole Ngrx store state to undefined using `Meta Reducers` by help of dispatching logout action

I want to logout from my app and before logging out I want to make sure I have completely cleared my app ngrx store states using MetaReducer with the help of this code
import { StoreModule, ActionReducer, MetaReducer } from '#ngrx/store';
export function logout(reducer: ActionReducer<any>): ActionReducer<any> {
return function (state, action) {
if (action.type === 'LOGOUT') {
state = undefined;
}
return reducer(state, action);
};
}
export const metaReducers: MetaReducer<any>[] = [logout];
when I am inside of a component which is subscribed to one of ngrx store state for example list of invoices, so when I am clicking log out button from here so getting this error in the console
and the line which gives me the error is
this.$invoice.takeWhile(() => this.isComponentAlive).subscribe(
invoice => {
this.permission = this.departmentService.getPermissionOfInvoiceCurrentDepartment(invoice.currentDepartmentId);
}
);
if I am changing the code to this
this.$invoice.takeWhile(() => this.isComponentAlive).subscribe(
invoice => {
if (invoice) {
this.permission = this.departmentService.getPermissionOfInvoiceCurrentDepartment(invoice.currentDepartmentId);
}
}
);
it solves the problem but I have many places like this so I want to one-stop-shop solution for this anyone could help me with this.

dispatch action triggering, but redux store not updating

Currently in my React-Redux app, I would like to use React to set the state of show to either false, or true. When set to true, the app will initialize. (There are multiple components, so it would make sense to do this using react/redux.)
However, my current issue is that even though I have connected my app using react redux, and my store using provider, the dispatch action will be called, without updating the store(I am using redux dev tools to double check, as well as in app testing).
I have attached the code I feel is relevant, however, the entire code base is available as a branch specifically made for this question here. I have spent quite sometime on this(actually an understatement) and any contributions would be greatly appreciated.
Component Relevant Code
hideBlock(){
const{dispatch} = this.props;
dispatch(hideBlock);
}
return(
<div className = "works">
<button id="show-block" type="button" className="show-hide-button" onClick={this.showBlock}>show</button>
<button id="hide-block" type="button" className="show-hide-button" onClick={this.hideBlock}>Hide</button>
</div>
);
function mapStateToProps(state) {
const {environment} = state;
return{
environment
}
}
export default connect(mapStateToProps)(Form);
Action
import * as types from "../constants/ActionTypes";
export function showBlock(show) {
return {
type: types.SHOW,
show: true
};
}
export function hideBlock(hide) {
return {
type: types.HIDE,
show: false
};
}
Reducer
import * as types from "../constants/ActionTypes";
const initialState = {
show: false
};
export default function environment(state = initialState, action) {
switch(action.type) {
case types.HIDE:
return Object.assign({}, state, {
type: types.HIDE
});
case types.SHOW:
return Object.assign({}, state, {
type: types.SHOW
});
default:
return state;
}
}
Thank you, and once again any help is very much so appreciated.
So, I asked a co-worker for help and it turns out that I was returning my action as an object instead of a function. So, for instance, changing the following code:
hideBlock(){
const{dispatch} = this.props;
dispatch(hideBlock);
}
to
hideBlock(){
const{dispatch} = this.props;
//change hideBlock to hideBlock()
dispatch(hideBlock());
}
solved the issue. Thanks Andrew!
It looks like state.show is set in initialState but never modified in any of the cases inside the reducer. The action has show: true, but the reducer never uses this to update the state.
Here's a simplified reducer that updates state.show based on the action's show field:
export default function environment(state = initialState, action) {
switch(action.type) {
case types.HIDE:
case types.SHOW:
return Object.assign({}, state, {
show: action.show
});
default:
return state;
}
}
Alternatively, because action.show and action.type have the same data, you could remove show from the actions and rely on the action type:
export default function environment(state = initialState, action) {
switch(action.type) {
case types.HIDE:
return Object.assign({}, state, {
show: false
});
case types.SHOW:
return Object.assign({}, state, {
show: true
});
default:
return state;
}
}

Categories

Resources