I'm trying to understand React- Redux basics, but I'm stuck in this particular case:
My code actions:
let todoId = 1
export const ADDTODO = 'AddTodo'
export const REMOVETODO = 'RemoveTodo'
export const TOINPROGRESS = 'ToInProgress'
export const TODONE = 'ToDone'
export function addTodo(payload){
return{
type: ADDTODO,
payload:{
status: 'Todo',
id: todoId++,
title: payload.title,
date:payload.date,
description:payload.description,
place:payload.place
}
}
}
export function removeTodo(todoId){
return{
type: REMOVETODO,
payload:todoId
}
}
export function toInProgress(todoId){
return{
type: TOINPROGRESS,
payload:todoId
}
}
export function toDone(todoId){
return{
type: TODONE,
payload:todoId
}
}
My attempt to reduce code:
import { addTodo, removeTodo, toInProgress, toDone } from '../actions';
const initialState = [];
const todos = (state = initialState, action) => {
switch(action.type) {
case 'AddTodo':
return[
...state, {
date:action.payload.date,
description:action.payload.description,
id:action.payload.id,
place:action.payload.place,
status:action.payload.status,
title:action.payload.title,
}
]
case 'RemoveTodo':
console.log(state)
return {
...state,
todos: state.todos.filter(todo => todo.id !== action.id)
}
case 'ToInProgress':
state.map(todo =>(todo.id===action.id)?{...todo,status:"InProgress"}:todo)
case 'ToDone':
state.map(todo =>(todo.id===action.id)?{...todo,status:"Done"}:todo)
default:
return state
}
}
The only working method from todos reducer is AddTodo, can't figure out to RemoveTodo, ToInProgress & ToDo to work.
I'm getting a TypeError at RemoveTodo that says "Cannot read property 'filter' of undefined"
and undefined returns from the another two methods.
In your case state is an array, so state.todos will be undefined. You can fix 'RemoveTodo' case with something like
case 'RemoveTodo':
return state.filter(todo => todo.id !== action.payload)
You missed return in other cases and you don't need to assign each property out of action.payload just pass it as is, this is how it would look
const todos = (state = initialState, action) => {
switch (action.type) {
case "AddTodo":
return [...state, action.payload];
case "RemoveTodo":
return state.filter((todo) => todo.id !== action.payload.id);
case "ToInProgress":
return state.map((todo) =>
todo.id === action.payload.id
? { ...todo, status: "InProgress" }
: todo
);
case "ToDone":
return state.map((todo) =>
todo.id === action.payload.id ? { ...todo, status: "Done" } : todo
);
default:
return state;
}
};
Related
I am working on the ngrx in angular 10 version
In Reducer, wanted to add action which will delete the user
while I tried the below code, getting an issue with it:
ERROR TypeError: Cannot delete property '1' of [object Array]
Not understanding this behaviour, as I just tried to remove an element from array by its position
I debug and seen that it is getting index of particular object in array
Getting error with state.users.splice(indx, 1);
export interface State {
users: IProduct[],
error: string
}
export const initialState: State = {
users: [],
error: ''
};
export function reducer(state = initialState, action: UserActions): State {
switch (action.type) {
case UserActionTypes.DeleteUser:
const indx = state.users.findIndex(user => user.id === action.payload.data.id);
state.users.splice(indx, 1);
return {
...state
}
case UserActionTypes.LoadUsers:
return {
...state
}
case UserActionTypes.LoadUsersSuccess:
return {
...state,
users: [...state.users, ...action.payload.data],
error: ''
}
case UserActionTypes.LoadUsersFailure:
return {
...state,
users: [],
error: action.payload.error
}
default:
return state;
}
}
Finally I got the solution
I assigned the data in new variable newState and then deleted the object from that
case UserActionTypes.DeleteUser:
const index = state.users.findIndex(user => user.id === action.payload.data.id);
let newState = [...state.users];
newState.splice(index, 1);
return {
users: newState,
error:''
}
I just changed the above code in given scenario
export interface State {
users: IProduct[],
error: string
}
export const initialState: State = {
users: [],
error: ''
};
export function reducer(state = initialState, action: UserActions): State {
switch (action.type) {
case UserActionTypes.DeleteUser:
const index = state.users.findIndex(user => user.id === action.payload.data.id);
let newState = [...state.users];
newState.splice(index, 1);
return {
users: newState,
error:''
}
case UserActionTypes.LoadUsers:
return {
...state
}
case UserActionTypes.LoadUsersSuccess:
return {
...state,
users: [...state.users, ...action.payload.data],
error: ''
}
case UserActionTypes.LoadUsersFailure:
return {
...state,
users: [],
error: action.payload.error
}
default:
return state;
}
}
I'm going to add an object to the array, the second time I want to add another object the whole array becomes number one and I end up with an error, my goal is to add a task to program Todo with Redux.
I also get this errors:
TypeError: Cannot read property 'length' of undefined
TypeError: undefined is not iterable (cannot read property Symbol(Symbol.iterator))
//todoReducer.js
import {ADD_TODO} from '../Actions/Todo';
const initialState = {
todos:[],
};
const handleAddTodo = (state, action) => {
const {todos} = state;
const newTodo =[...todos, {
id: todos.length + 1,
text: action.title,
isComplete: false,
}]
return (
todos.push(newTodo)
)
}
export default function todoRDS(state = initialState, action) {
switch (action.type) {
case ADD_TODO:
return handleAddTodo(state, action)
default:
return state
}
}
Change your return function you return wrong value. You need to return the state
const handleAddTodo = (state, action) => {
const {todos} = state;
return {
...state,
todos: [...todos, {
id: todos.length + 1,
text: action.title,
isComplete: false,
}]
}
}
export default function todoRDS(state = initialState, action) {
switch (action.type) {
case ADD_TODO:
return {...state, todos: [...state.todos, { id: state.todos.length +1, title: action.title, isComplete: false }] }
default:
return state
}
}
state is unmutable in react and redux you need to create a new state with old state values and add your new todo inside that new object. If you still want to use handeAddTodo try this:
const handleAddTodo = (state, action) => {
return {...state, todos: [...state.todos, { id: state.todos.length +1, title: action.title, isComplete: false }] }
}
I get:
TypeError: Cannot read property 'concat' of undefined
however, I have define the 'orders' array in my initialState.
Does somebody know the reason?
import * as actionTypes from '../actions/actionTypes.js';
import { updateObject } from '../utility.js';
const initialState = {
orders: [],
loading: false,
purchased: false
};
const purchaseInit = (state, action) => {
return updateObject(state, { purchased: false });
};
const purchaseBurgerStart = (state, action) => {
return updateObject(state, { loading: true });
};
const purchaseBurgerSuccess = (state, action) => {
const newOrder = updateObject(action.orderData, { id: action.orderId });
return updateObject(state, {
loading: false,
purchased: true,
orders: state.orders.concat(newOrder)
});
};
const purchaseBurgerFail = (state, action) => {
return updateObject(state, { loading: false });
};
const fetchOrdersStart = (state, action) => {
return updateObject(state, { loading: true });
};
const fetchOrdersSuccess = (state, action) => {
return updateObject(state, {
orders: action.orders,
loading: false
});
};
const fetchOrdersFail = (state, action) => {
return updateObject(state, { loading: false });
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.PURCHASE_INIT: return purchaseInit(state, action);
case actionTypes.PURCHASE_BURGER_START: return purchaseBurgerStart(state, action);
case actionTypes.PURCHASE_BURGER_SUCCESS: return purchaseBurgerSuccess(state, action);
case actionTypes.PURCHASE_BURGER_FAIL: return purchaseBurgerFail(state, action);
case actionTypes.FETCH_ORDERS_START: return fetchOrdersStart(state, action);
case actionTypes.FETCH_ORDERS_SUCCESS: return fetchOrdersSuccess(state, action);
case actionTypes.FETCH_ORDERS_FAIL: return fetchOrdersFail(state, action);
default: return { state };
}
};
export default reducer;
Please check the state parameter this parameter does not have a state array, you can use a console.log to check what this parameter has.
I think you are missing to assign the state as initial state in the parameter.
const purchaseBurgerSuccess = (state = initialState, action)
1) This line...
case actionTypes.PURCHASE_BURGER_SUCCESS: return purchaseBurgerSuccess(state, action);
...as with all the other lines in that switch statement should be returning a new state.
So in purchaseBurgerSucess you need to make sure you're returning a new state using the state you're passing in as an argument:
const purchaseBurgerSuccess = (state, action) => {
const newOrder = updateObject(action.orderData, { id: action.orderId });
// Spread out the state you pass in as an argument
// and update those properties that have changed
return {
...state,
loading: false,
purchased: true,
orders: state.orders.concat(newOrder)
};
};
Note: your other functions fall into this trap too so those will also need to be updated.
2) Your default case in your switch statement should be:
default: return state;
For practice with Redux, I have in index.js
const state = [
{
resort: 'Jay Peak',
date: '2018-2-22',
powder: true,
backcountry: false,
},
];
const action = {
type: constants.ADD_DAY,
payload: {
resort: 'Mad River Glen',
date: '2018-3-14',
powder: true,
backcountry: false,
},
};
const nextState = allSkiDays(state, action);
console.log(`
initial state: ${JSON.stringify(state)}
action: ${JSON.stringify(action)}
new state: ${JSON.stringify(nextState)}
`);
and my reducers for composition,
export const skiDay = (state = [], action) => {
action.type === constants.ADD_DAY ? action.payload : state;
};
export const allSkiDays = (state = [], action) => {
switch (action.type) {
case constants.ADD_DAY:
return [...state, skiDay(null, action)]; // COMPOSITION!!!! use skiDay()
default:
return state;
}
};
and I keep getting this result,
initial state: [{"resort":"Jay Peak","date":"2018-2-22","powder":true,"backcountry":false}]
action: {"type":"ADD_DAY","payload":{"resort":"Mad River Glen","date":"2018-3-14","powder":true,"backcountry":false}}
new state: [{"resort":"Jay Peak","date":"2018-2-22","powder":true,"backcountry":false},null]
I've tried many things why is null still being spread onto the array and not the new object?
This reducer is not returning the next state.
export const skiDay = (state = [], action) => {
action.type === constants.ADD_DAY ? action.payload : state;
};
Do this instead:
export const skiDay = (state = [], action) => {
return action.type === constants.ADD_DAY ? action.payload : state;
};
I have action creator to get data from API and have another action creator for loading status and want change loading status when data completely fetched.
Now, I wrote following codes but not working good, Loading status changes to false before data fetched completely.
My ActionCreator:
export const loadingStatus = (bool) => {
return {
type: Constants.LOADING_STATUS,
isLoading: bool
};
}
const allFlashCards = (action) => {
return{
type: Constants.ALL_CARD,
...action
}
};
export const fetchAllFlashCards = () => {
return (dispatch) => {
dispatch(loadingStatus(true));
return axios.post(API.DISPLAY_ALL_CARDS)
.then((data)=>{
console.warn(data);
dispatch(allFlashCards(data));
dispatch(loadingStatus(false));
}).catch((error)=>{
console.warn(error)
});
}
};
and my Reducer:
const FlashCard = (state = [], action) => {
switch (action.type) {
case Constants.ADD_CARD:
return {...state, data: action.data};
break;
case Constants.ALL_CARD:
return {...state, data: action};
break;
default:
return state;
}
};
export const Loading = (status= false, action) => {
switch (action.type) {
case Constants.LOADING_STATUS:
return action.isLoading;
break;
default:
return status;
}
}
and in my component:
componentDidMount() {
this.props.fetchCards();
}
render() {
return(
<div>
{this.props.loading ?
<Loading/> :
Object.keys(this.props.cards.data).map(this.renderCard)
}
</div>
);
}
const mapStateToProps = (state) => ({
cards: state.main,
loading: state.loading
});
const mapDispatchToProps = (dispatch) => ({
fetchCards: bindActionCreators(fetchAllFlashCards, dispatch)
});
and combineReducer is:
import { combineReducers } from 'redux';
import FlashCard , { Loading } from './FlashCard.js';
import { routerReducer } from "react-router-redux";
export default combineReducers({
main: FlashCard,
loading: Loading,
routing: routerReducer
});
In my page, I have an error in console and it's:
Uncaught TypeError: Cannot read property 'data' of undefined and if put my codes in timeout fixed my bug :/
What should i do?
Your default state is wrong here:
const FlashCard = (state = [], action) => {
switch (action.type) {
case Constants.ADD_CARD:
return {...state, data: action.data};
break;
case Constants.ALL_CARD:
return {...state, data: action};
break;
default:
return state;
}
};
It should be an empty object {} instead of an empty array [], since you're returning objects.
This code
export const fetchAllFlashCards = () => {
return (dispatch) => {
dispatch(loadingStatus(true));
return axios.post(API.DISPLAY_ALL_CARDS)
.then((data)=>{
console.warn(data);
dispatch(allFlashCards(data));
dispatch(loadingStatus(false));
}).catch((error)=>{
console.warn(error)
});
}
};
Looks completely fine. loadingStatus(false) should not be called before setting the flash cards. Your reducers and action creators are synchronous (as they should). So, nothing of note there.
I saw that you're using action.data on the Constants.ADD_CARD action case, but in your code you do not dispatch any actions with that type. Do you do it somewhere else? Maybe that's where the error is?
EDIT:
Another place that you're using .data is in your renderer: this.props.cards.data. What's the value of the state.main?
How are you creating your rootReducer? It should be something like this:
const rootReducer = combineReducers({
main: FlashCard,
loading: Loading,
});
Are you using main there? Or maybe cards?
Finally, I fixed my problem:
In my actionCreator change fetchAllFlashCards method to following:
export const fetchAllFlashCards = () => {
return (dispatch) => {
dispatch(loadingStatus(true));
return axios.post(API.DISPLAY_ALL_CARDS)
.then(({data})=>{
dispatch(allFlashCards(data));
dispatch(loadingStatus(false));
}).catch((error)=>{
console.warn(error)
});
}
};
and in reducer change FlashCard reducer to following:
const FlashCard = (state = [], action) => {
switch (action.type) {
case Constants.ADD_CARD:
return {...state, data: action.data};
break;
case Constants.ALL_CARD:
return {...state, data: action.data};
break;
default:
return state;
}
};