I just started to get familiar with Redux and I'm stuck on this error.I'm getting error Cannot read property 'todos' of undefined,On code below which is in component. :
const mapStateToProps = (item) => {
return {
todos: item.todos,
numberPerPage: item.numberPerPage,
currentPage: item.currentPage
};
};
This is my Storage :
const initialState = {
todos: [],
numberPerPage: 10,
currentPage: 1
}
const reducer = (state =initialState, action) => {
const { items, numberPerPage, currentPage } = state;
switch (action.type) {
case actionTypes.ADD_ITEM:
return {
...state,
todos: [
...items,
{
value: action.value,
checked: action.checked,
_id: action.id,
},
],
currentPage: Math.ceil((items.length + 1) / numberPerPage)
};
case actionTypes.GET_ALL:
return {
...state,
todos: action.items,
};
}
}
export default reducer;
Any Suggestions?
You do not have a default case in your reducer which is what causes the error.
When the reducer is initially set up, redux dispatches an ##redux/INIT action at which point of time your reducer returns undefined resulting in an error when you try to access the state value
Adding a default case to return state will overcome this issue and solve your problem
const reducer = (state =initialState, action) => {
const { items, numberPerPage, currentPage } = state;
switch (action.type) {
case actionTypes.ADD_ITEM:
return {
...state,
todos: [
...items,
{
value: action.value,
checked: action.checked,
_id: action.id,
},
],
currentPage: Math.ceil((items.length + 1) / numberPerPage)
};
case actionTypes.GET_ALL:
return {
...state,
todos: action.items,
};
default: return state;
}
}
Related
Hello i've been struggling with this for a while
i want to start with a initial state like so:
const initialState = {
cart: { cartItems: {}}
};
but i already have a couple reducers that make my initial state like so:
{
productList?:undefined,
productDetails?:undefined
}
but i get the error:
Type '{cart:{cartItems:{}}' has no properties in common with type '{productList?: undefined, productDetails? :undefined}'
my reducer looks like:
export const productListReducer = (state = { products: [] as any[] }, action:productListReducer) => {
switch (action.type) {
case PRODUCT_LIST_REQUEST:
return {
loading: true,
products: [] as any[],
};
case PRODUCT_LIST_SUCCESS:
return {
loading: false,
products: action.payload,
};
case PRODUCT_LIST_FAIL:
return {
loading: false,
error: action.payload,
};
default:
return state;
}
};
const reducer = combineReducers({
productList: productListReducer,
});
const store = createStore(
reducer,
initialState,
composeEnhancers(applyMiddleware(thunk))
);
any help is appreciated, thanks
My user structure is:
user = {
email: 'email',
flashcards: []
}
And i would like to add data into user's flashcards array (using redux)
My user-reducer
import { UserActionTypes } from './user.types';
const INITIAL_STATE = {
currentUser: null,
};
// GETS STATES OBJ AND RECIVES AN ACTION
const userReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case UserActionTypes.SET_CURRENT_USER:
return {
...state,
currentUser: action.payload,
};
case UserActionTypes.ADD_FLASHCARD:
return {
...state,
currentUser: action.payload,
};
default:
return state;
}
};
export default userReducer;
user-actions
export const addFlashCard = user => ({
type: UserActionTypes.ADD_FLASHCARD,
payload: user.flashcards,
});
And when i'm doing so my payload is undefined.
Could you give me some hints?
You are currently overwriting currentUser with the value of user.flashcards from the redux action. To add new flashcards, the ADD_FLASHCARD branch of your reducer should look more like this:
case UserActionTypes.ADD_FLASHCARD:
return {
...state,
currentUser: {
...state.currentUser,
flashcards: [
...state.currentUser.flashcards,
...action.payload
]
}
};
My issue is that I want to fetch all products from the database and set them into the Redux initial state, to do this I did an action SET_PRODUCTS_LIST and in the action. payload I simply passed the products fetched in the component (I am using next js), all works fine but when I try to fire another action like ADD_PRODUCT_TO_CART the products in the initial state are gone which it results impossible to add more than 1 product to the cart.
Inside my component:
function Header({ cartProps, setProducts }) {
useEffect(async () => {
const products = await getProducts();
setProducts(products);
}, []);
}
const mapStateToProps = (state) => {
return {
cartProps: state.cartState,
};
};
export default connect(mapStateToProps, {
setProducts,
})(Header);
the action to set products:
import { SET_PRODUCTS_LIST } from "./types";
export const setProducts = (products) => {
return (dispatch) => {
dispatch({
type: SET_PRODUCTS_LIST,
payload: products,
});
};
};
My cart reducer:
const initialState = {
itemNumbers: 0,
cartCost: 0,
products: [],
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case SET_PRODUCTS_LIST: {
return {
...state,
products: action.payload,
};
}
case ADD_PRODUCT_TO_CART: {
//let addQuantity = {
// ...state.products.filter((p) => p.productName === action.paylaod),
// };
console.log(state.products);
return {
itemNumbers: state.itemNumbers + 1,
};
}
default:
return state;
}
};
export default reducer;
maybe I am completely doing wrong the logic about fetching the products in order to have them in the initial state.
const initialState = {
itemNumbers: 0,
cartCost: 0,
products: [],
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case SET_PRODUCTS_LIST: {
return {
...state,
products: action.payload,
};
}
case ADD_PRODUCT_TO_CART: {
//let addQuantity = {
// ...state.products.filter((p) => p.productName === action.paylaod),
// };
console.log(state.products);
return {
...state
itemNumbers: state.itemNumbers + 1,
};
}
default:
return state;
}
};
export default reducer;
You should always return state but in ADD_PRODUCT_TO_CART case you return only
{
itemNumbers: state.itemNumbers + 1,
}
so you need to add ...state before itemNumbers: state.itemNumbers + 1,
I have read many articles and examples, but I dont find a method to simplify a nested state. Is this possible to udpate a state without using a rest operator by property like the partial code below ?
const initialState = {
currentTag: 'fr-FR',
locales: {
components: [],
},
};
const setComponentsLocales = (state, payload) => ({
...state,
[payload.oid]: payload.locales,
});
const localesReducer = (state, action) => {
switch (action.type) {
case types.SET_COMPONENT_LOCALES:
return {
...state,
components: setComponentsLocales(state.components, action.payload),
};
default:
return state;
}
};
export default (state = initialState, action) => {
switch (action.type) {
case types.SET_LANGUAGE:
return {
...state,
currentTag: action.payload.tag,
};
default:
return {
...state,
locales: localesReducer(state.locales, action),
};
}
};
I got a problem when do objectAssign to change the state in store into a new data from server, It always get a null as the result.
i call my action in onEnter function(React-Router)
export function GET_SetupTabTitles() {
store.dispatch(getSetupTabTitles());
}
this is my action :
import {
TOGGLE_DRAWER_IN_APPBAR,
GET_SETUP_TAB_TITLES,
} from '../constants/actionTypes';
import axios from 'axios';
const ROOT_URL = 'http://localhost:8000';
export function toggleDrawerInAppBar(open){
return { type: TOGGLE_DRAWER_IN_APPBAR, openStatus: open }
}
export function getSetupTabTitles(){
return function(dispatch){
axios.get(`${ROOT_URL}/api/component/getSetupTabTitles`)
.then(response => {
dispatch({type: GET_SETUP_TAB_TITLES,
payload: response
});
});
}
}
this is my initial state on reducer :
export default {
auth: {
authenticated: (localStorage.getItem('laravel_user_token') !== null),
userinfo: {
name: null
},
error:""
},
comp: {
openDrawerStatus: false,
setupTabTitles: null,
}
};
and this is my reducer :
import {
TOGGLE_DRAWER_IN_APPBAR,
GET_SETUP_TAB_TITLES,
} from '../constants/actionTypes';
import initialState from './initialState';
import objectAssign from 'object-assign';
const compReducer = (state = initialState.comp, action) => {
switch (action.type) {
case TOGGLE_DRAWER_IN_APPBAR:
return objectAssign({}, state, {openDrawerStatus: action.openStatus});
case GET_SETUP_TAB_TITLES:
console.log(action.payload.data);
return objectAssign({}, state, {setupTabTitles: action.payload.data});
default:
return state;
}
};
export default compReducer;
when i do console.log inside
case GET_SETUP_TAB_TITLES:
it show :
Array[2]0: 0:Object 1:Object
On using JSON.stringify() it shows me [{"tabTitle":"Events"},{"tabTitle":"Tasks"}]
but my state (setupTabTitles) didn't change at all.
i do try this one :
case GET_SETUP_TAB_TITLES:
state.setupTabTitles.push(action.payload.data[0]);
return state;
it work, but i don't want to direct change the state.
You don't need to import ojectAssign from 'object-assign'; when you make use of the current ES6 syntax in your code. You only need Object.assign. Also since your action.data.payload is an array and you need to append to an array you can use the spread operator like
return {
...state,
setupTabTitles: [...state.setupTabTitles, action.payload.data]
}
Also you need to initialise you componentState to be an empty array and not null or undefined. Change that to below code
export default {
auth: {
authenticated: (localStorage.getItem('laravel_user_token') !== null),
userinfo: {
name: null
},
error:""
},
comp: {
openDrawerStatus: false,
setupTabTitles: [],
}
};
Try it like below
const compReducer = (state = initialState.comp, action) => {
switch (action.type) {
case TOGGLE_DRAWER_IN_APPBAR:
return Object.assign({}, state, {openDrawerStatus: action.openStatus});
case GET_SETUP_TAB_TITLES:
console.log(action.payload.data);
return {
...state,
setupTabTitles: [...state.setupTabTitles, ...action.payload.data]
}
default:
return state;
}
};
The syntax of objectAssign is different from what I use, you can see it here
var state = {
openDrawerStatus: false,
setupTabTitles: [],
}
var payload = [{"tabTitle":"Events"},{"tabTitle":"Tasks"}]
console.log( {
...state,
setupTabTitles: [...state.setupTabTitles, ...payload]
});
As you are already using ES6, you could just use the object spread operator and get rid of the object-assign library, it would be like this:
import {
TOGGLE_DRAWER_IN_APPBAR,
GET_SETUP_TAB_TITLES,
} from '../constants/actionTypes';
import initialState from './initialState';
const compReducer = (state = initialState.comp, action) => {
switch (action.type) {
case TOGGLE_DRAWER_IN_APPBAR:
return { ...state, openDrawerStatus: action.openStatus };
case GET_SETUP_TAB_TITLES:
return { ...state, setupTabTitles: action.payload.data };
default:
return state;
}
};
export default compReducer;
In your initial state, I would change setupTabTitle from null to an empty array []:
setupTabTitles: [],
And in your reducer, append data to this array:
const compReducer = (state = initialState.comp, action) => {
switch (action.type) {
...
case GET_SETUP_TAB_TITLES:
return {
...state,
setupTabTitles: [
...state.setupTabTitles,
...action.payload.data
]
}
...
}
};
Or if you don't want to append, just replace, I would do:
setupTabTitles: [
...action.payload.data
]