I have setup redux with a reducer that updates an array of time slots for a given date. Every time I change the date, the reducer updates the state (I have console logged the state change which happens as expected in my mapStateToProps function), however, the redux connect function doesn't detect that the array of time slots has been updated. Here is my reducer:
import { SHOW_ERROR, HIDE_ERROR, SHOW_SUCCESS, HIDE_SUCCESS, UPDATE_TIME_SLOTS } from '../actions/types';
const INITIAL_STATE = { error: null, success: null, timeSlots: null };
export default function (state = INITIAL_STATE, action) {
switch (action.type) {
case SHOW_ERROR:
return { ...state, error: action.payload };
case HIDE_ERROR:
return { ...state, error: action.payload };
case SHOW_SUCCESS:
return { ...state, success: action.payload };
case HIDE_SUCCESS:
return { ...state, success: action.payload };
case UPDATE_TIME_SLOTS:
return { ...state, timeSlots: action.payload };
default:
break;
}
return state;
}
The time slots is always an array like so:
["08:30", "09:15", "10:00", "10:45", "11:30", "12:15", "13:00", "13:45", "14:30", "15:15", "16:00", "16:45"]
Here is my mapStateToProps function which then uses redux connect to send these props to my BookingForm (this is where my console.log detects the change in state correctly:
function mapStateToProps(state) {
console.log(state.public.timeSlots);
return {
timeSlots: state.public.timeSlots
};
}
export default connect(mapStateToProps, { create, getAvailableSlots })(form(BookingForm));
Every time I click on a date in my form the state is updated with the new array, however, my connect function doesn't detect this update and therefore the BookingForm component doesn't re-render. I am assuming this is to do with the array not immutably being updated, however, I haven't found a solution to designing it so that it is updated immutably.
Any help?
You are probably sending the same array instance, and the connect function is checking for reference changes only! btw: same for objects.
Using the same array will not get detected by the connect, you have to create a new array every time you want to update the state.
How about you let the reducer deal with that?
So, once the user clicks a date on your form, send an action to record that, say "DATE_CLICKED" action, and in the reducer, add the date to a new array along with all the previous values.
Like this:
return [...state.dates, action.date]
Related
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.
I am using react with redux for my application, I have state as array of objects, in reducer for adding new element in an array the entire state is getting changed to 4 instead of showing new element along with old ones and also giving me error "todos.map is not a function".
My State
export const todos = [
{
id: "1",
item: "Buy Milk"
},
];
reducer
export const reducer = (state = todos, action) => {
let newTodos;
switch (action.type) {
case ADD_TODO:
newTodos = state;
newTodos = newTodos.push(action.payload);
return newTodos;
case DELETE_TODO:
break;
case UPDATE_TODO:
break;
default:
return state;
}
};
action.payload(ADD_TODO) in redux dev tool
type(pin): "ADD_TODO"
payload(pin)
id(pin): "870f7b60-5267-11eb-85d2-cbd5e33df548"
item(pin): "ghgh"
After dispatching action getting error "todos.map is not a function" and state is getting changed to
(pin): 4
push returns the length of the new array, not the array itself. Instead you need to create a new array without mutating the original:
case ADD_TODO:
return [...state, action.payload];
You mutate state and then assign state to the wrong value in add todoe:
[].push({}) === 1;
try this instead:
return [...state, action.payload]
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.
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
}
I can't seem to reset the default state; how do I do that? I tried this but all it does is add state to state and calls it undefined.
const initialState = {
name: null,
coins: 0,
image: null,
};
export default function reducer(state = initialState, action = {}) {
switch (action.type) {
case types.ADD_GROUP_COINS:
return {
...state,
coins: state.coins + action.coins
};
case types.DELETE_GROUP:
return {
state: undefined
};
default:
return state;
}
}
To reset the state to the initialState, simply return initialState, like this:
case types.DELETE_GROUP:
return initialState;
Remember that in the reducer, the object that you return is the new state. You don't need to wrap it in another object, like this:
return {
state: ...
}
That will give you a property called state in your state, which isn't at all what you need.
From the documentation:
The reducer is a pure function that takes the previous state and an action, and returns the next state.
It's easy to get confused about this, so be careful! Take a look at your default case if you're still not quite sure: you're returning the old state variable as the new state, not { state: state } which you are essentially doing with the current DELETE_GROUP handler.