Redux How to insert new Sub data in state - reactjs

I'm making a todo app and using redux for state management. My todo state is made up of nested arrays.
const initialState = {
todos: [
{
id: 1,
name: "task1",
subdata: [
{
id: 101,
name: "subtask1",
complete: false,
},
{
id: 102,
name: "subtask2",
complete: true,
},
],
},
{
id: 2,
name: "task2",
subdata: [
{
id: 103,
name: "subtask3",
complete: false,
},
{
id: 104,
name: "subtask4",
complete: true,
},
],
},
Reducers:
export default function reducer(state = initialState, action) {
switch (action.type) {
case ADD_TODO:
const newTodo = state.todos.concat(action.payload);
return { ...state, todos: newTodo };
case ADD_SUBTODO:
const newSubtodo = action.payload;
?????????????????????????????????????????????
How can i append new subtodo to initialstate?
I used the immer library, but I want to do it the traditional way, for example the spread operator. I would be glad if you help.

You could do something like...
// send payload as {id:1,newSubtodo: newSubtodo}
case ADD_SUBTODO:
const newSubtodo = action.payload.newSubtodo;
//id is the task/todo id of which you want to add a new subdata
const newTask = initialState.todos.find(i=>i.id==action.payload.id)
//id is the task/todo id of which you want to add a new subdata
newTask.subdata.push(newSubtodo)
return {...initialState,todos:[...initialState.todos,newTask]}
Note: Using nested objects as state in React Js is not a good
practice.

Related

How to update nested state in redux

I'm a bit stuck with redux. I want to create reducer that can update state onClick with data that provided in each button.
Here's my TabSlice.ts
interface ITabContext {
tabIndex: number,
posterUrlFetch: string,
tabData: {
fetchUrl: string;
title: string;
}[]
}
const initialState = {
tabIndex: 0,
posterUrlFetch: 'movie/popular',
tabData: [
{ fetchUrl: 'movie/popular', title: 'Trending' },
{ fetchUrl: 'movie/upcoming', title: 'Upcoming' },
{ fetchUrl: 'tv/popular', title: 'TV Series' },
]
}
const tabSlice = createSlice({
name: 'tab',
initialState: initialState as ITabContext,
reducers: {
changeTab(state, action: PayloadAction<ITab>) {
const newItem = action.payload;
return state = {
tabIndex: newItem.tabIndex,
posterUrlFetch: newItem.posterUrlFetch,
tabData: [
{ fetchUrl: newItem.posterUrlFetch, title: newItem.posterUrlFetch },
]
}
}
}
})
Then I dispatch changeTab in my component and create function onClick:
const click = () => dispatch(changeTab({
tabIndex: 1,
posterUrlFetch: 'movie/popular',
tabData: [
{
fetchUrl: 'tv/latest',
title: 'TV Latest'
},
{
fetchUrl: 'movie/popular',
title: 'Popular'
},
{
fetchUrl: 'movie/latest',
title: 'Latest'
},
]
}));
As i click some info updates, but in tabData I have only first object. How to make it to push all data to tabData, not only first one? Thanks!
Remove return state = {} from your reducer function and instead return the object as a whole.
return {
tabIndex: newItem.tabIndex,
posterUrlFetch: newItem.posterUrlFetch,
tabData: newItem.tabData,
};
For the payload's tabData you can pass newItem.tabData

Redux-toolkit reducer partially changing state

Hi everyone I have some redux-toolkit issue that I believe comes from immer but cannot be sure.
Using createSlice I am creating reducer to manage open/close/move/add of UI tabs. all of the reducers are working properly except the closeTab reducer.
This is an example of the state at the moment of execution of closeTab reducer:
[{
id: 1,
active: false,
tittle: 'Cute',
},
{
id: 2,
active: true,
tittle: 'Cute',
}]
This is the entire createSlice
export const tabsSlice = createSlice({
name: "tabs",
initialState,
reducers: {
moveTab: (tabs, action: PayloadAction<{ dragIndex: number, hoverIndex: number }>) => {
tabs.splice(action.payload.hoverIndex, 0, tabs.splice(action.payload.dragIndex, 1)[0]);
},
selectTab: (tabs, action: PayloadAction<number>) => {
tabs.forEach(tab => tab.active = tab.id === action.payload);
},
closeTab: (tabs, action: PayloadAction<number>) => {
const isCurrentActive = tabs[action.payload].active;
tabs.splice(action.payload, 1);
if (isCurrentActive && tabs.length !== 0) {
const newActive = action.payload === 0 ? 0 : action.payload - 1;
tabs[newActive].active = true;
}
if (tabs.length === 0) {
tabs.push({
id: Date.now(),
active: true,
tittle: 'Cute2'
})
}
},
addTab: (tabs) => {
tabs.forEach(tab => tab.active = false)
tabs.push({
id: Date.now(),
active: true,
tittle: 'Cute2'
})
},
}
})
As mentioned moveTab, selectTab and addTab work perfectly but when closeTab is executed,
the array is spliced (the tab is removed), but the active property of the state is not changed. And I am sure that at the end of the reducer the state is as I want it.
State should be changed from:
[{
id: 1,
active: false,
tittle: 'Cute',
},
{
id: 2,
active: true,
tittle: 'Cute',
}]
to
[{
id: 1,
active: true,
tittle: 'Cute',
}]
But in the component I am receiving this:
[{
id: 1,
active: false,
tittle: 'Cute',
}]
The array length is changed, but not the active property
false issue selectTab overriding closeTab

Why does forEach loop only set the last value if finds to state. ReactJS

const CategoriesData = [
{
name: "Category1",
isActive: true,
children: [
{
name: "Category1Child",
isActive: false,
}
]
},
{
name: "Category2",
isActive: false,
},
{
name: "Category3",
isActive: true,
children: [
{
name: "Category3Child",
isActive: false,
}
]
}
];
const [disabledCategories, setDisabledCategories] = useState([]);
function notActiveCategories(categories) {
// Loop logs out at least 7 isActive: false categories.
categories.forEach((category) => {
if (category.isActive) notActiveCategories(category.children);
if (!category.isActive) {
setDisabledCategories([...disabledCategories, category]);
console.log(category);
}
});
};
useEffect(() => {
notActiveCategories(CategoriesData);
console.log(disabledCategories); // Only 1 category is in the array.
}, []);
I feel like the function the loop is in calling itself is causing the disabledCategories state to revert to when it was empty and that is leading to only the last step of the foreach to be set.
So how would i get this to loop through the categories array and have the disabledCategories state to contain all of the category objects that have isActive: false.
Which in the example of CategoriesData above, it would mean that the disabledCategories state would contain:
[
{
name: "Category1Child",
isActive: false,
},
{
name: "Category2",
isActive: false,
},
{
name: "Category3Child",
isActive: false,
},
];
Try changing your setDisabledCategories to use the previous state param that comes from setState:
setDisabledCategories(prevState => [...prevState, category])
When multiple setState calls are batched together you need to be careful so they don't override each other. Using this method ensures that your setState calls are "chained" so you always get the updated state.
Way 1: Affect after recursive loop
function notActiveCategoriesRecusive(categories) {
let notActive = []
categories.forEach((category) => {
if (category.isActive) notActive = [...notActive, ...(notActiveCategories(category.children))];
if (!category.isActive) {
notActive.push(category)
}
});
return notActive
};
function notActiveCategories(categories) {
setDisabledCategories(notActiveCategoriesRecusive(categories)
}
Way 2: Get the last state because it doesn't has time to refresh
function notActiveCategories(categories) {
categories.forEach((category) => {
if (category.isActive) notActiveCategories(category.children);
if (!category.isActive) {
setDisabledCategories(oldState => ([...oldState, category]))
}
});
};
I'd only call setState once with the filtered array:
const findInactive = data =>
data.filter(e => !e.isActive)
.concat(...data.filter(e => e.children)
.map(e => findInactive(e.children)))
;
const categoriesData = [ { name: "Category1", isActive: true, children: [ { name: "Category1Child", isActive: false, } ] }, { name: "Category2", isActive: false, }, { name: "Category3", isActive: true, children: [ { name: "Category3Child", isActive: false, } ] } ];
const inactive = findInactive(categoriesData)
// the following is neeeded if it's possible for a
// node to have children and be inactive
// .map(({name, isActive}) => ({name, isActive}))
;
console.log(inactive);
//setDisabledCategories(inactive); // one time in React
This makes the code a lot easier to reason about and decouples React's API out from the filtering logic, which can be moved out to a generic function agnostic of React.
As others have mentioned, if you do want to call setState multiple times as a batch update, you can use the prevState callback to chain the updates: setDisabledCategories(prevState => [...prevState, category]);.

how to update state with reducer, when my state is an array not an object

I have a problem with returning new state in my reducer function. My state is an array of objects. Each object has two key value pairs category: '' and items: [{}, {}, {}].
const initialState = [
{
category: 'vegetables',
items: [
{
id: 1,
name: 'carrot',
amount: 3,
unit: 'pc',
},
{
id: 2,
name: 'potato',
amount: 1,
unit: 'kg',
},
{
id: 3,
name: 'broccoli',
amount: 2,
unit: 'pc',
},
],
},
{
category: 'fruits',
items: [
{
id: 4,
name: 'orange',
amount: 4,
unit: 'pc',
},
{
id: 5,
name: 'blueberries',
amount: 250,
unit: 'g',
},
],
},
{
category: 'drinks',
items: [
{
id: 6,
name: 'Coca Cola',
amount: 2,
unit: 'l',
},
{
id: 7,
name: 'Grapefruit juice',
amount: 1,
unit: 'l',
},
{
id: 8,
name: 'Water',
amount: 1,
unit: 'l',
},
],
},
{
category: 'cereal products',
items: [
{
id: 9,
name: 'Cereal',
amount: 2,
unit: 'pack',
},
{
id: 10,
name: 'Muesli',
amount: 1,
unit: 'kg',
},
],
},
];
I want to remove items inside items array and leave the rest unchanged. The problem is within my reducer function and my switch statement is returning a wrong value:
const rootReducer = (state = initialState, action) => {
switch (action.type) {
case 'REMOVE_ITEM':
state = [
state.map((element) => element.items.filter((item) => item.id !== action.payload.id)),
];
return state;
default:
return state;
}
};
I'm not asking for a quick fix but just a hint would be much appreciated.
Thank you, guys!
Your reducer should look like this I think:
const rootReducer = (state = initialState, action) => {
switch (action.type) {
case 'REMOVE_ITEM':
return state.map(element => ({
...element,
items: element.items.filter((item) => item.id !== action.payload.id))
})
default:
return state;
}
};
This solution assumes "item.id" values are unique within "initialState" scope.
const rootReducer = (state = initialState, action) => {
switch (action.type) {
case 'REMOVE_ITEM':
state = state.map(element =>
Object.assign({}, element,
{items: element.items.filter(item => item.id !== action.payload.id)}
)
);
return state;
default:
return state;
}
};

Which reducer gets used when using dispatch in mapDispatchToProps? [duplicate]

This question already has answers here:
All reducers will be invoked when an action is dispatched?
(3 answers)
Closed 2 years ago.
I am learning about Redux and I have two reducers, a contactReducer to show contacts on the page and a testReducer to just mess around with. In one of my component files I have this function:
const mapDispatchToProps = (dispatch) => ({
getContacts: () => dispatch({ type: "TEST_ACTION" }),
});
These are my two reducer files:
contactReducer:
import { GET_CONTACTS } from "../actions/types";
const initialState = {
contacts: [
{
id: 1,
name: "John Doe",
email: "john#gmail.com",
phone: "555-555-5555",
},
{
id: 2,
name: "Karen Williams",
email: "karen#gmail.com",
phone: "444-444-4444",
},
{
id: 3,
name: "Henry Johnson",
email: "henry#gmail.com",
phone: "333-333-333",
},
],
};
export default function (state = initialState, action) {
switch (action.type) {
case GET_CONTACTS:
return {
...state,
};
default:
console.log("testing action in contactReducer");
return state;
}
}
and testReducer:
import { GET_CONTACTS } from "../actions/types";
const initialState = {
contactsTest: [
{
id: 1,
name: "ffffffffffff",
email: "john#gmail.com",
phone: "555-555-5555",
},
{
id: 2,
name: "ggggggggggggg",
email: "karen#gmail.com",
phone: "444-444-4444",
},
{
id: 3,
name: "aaaaaaaaaaaaaa",
email: "henry#gmail.com",
phone: "333-333-333",
},
],
};
export default function (state = initialState, action) {
switch (action.type) {
case "TEST_ACTION":
return {
...state,
};
default:
console.log("testing action");
return state;
}
}
So, what I noticed from the console.log statements in the reducer files was that for every contact, both the contactReducer and testReducer's function was called with this line:
getContacts: () => dispatch({ type: "TEST_ACTION" }),
});
What if I have multiple reducers but I only want to call one of their functions for dispatch, what would I do?
combineReducers, Is a helper function in redux that helps you divide your reducers. take a look at this link: LINK

Resources