In my application, I want to get the following state structure:
{
appName: "Combine Reducers App",
fruits: {
apples: [
{ id: 0, name: "apple1", saved: true },
{ id: 1, name: "apple2", saved: true }
],
oranges: [
{ id: 0, name: "orange1", saved: true },
{ id: 1, name: "orange2", saved: true }
],
loaded: false,
saved: true
}
}
I need individual reduxers to be responsible for managing the state of apples and oranges.
Can you tell me how to type and combine nested reducers?
Source code: https://stackblitz.com/edit/react-redux-ts-nested-reducers?file=store.ts
If you click the test orange reducer button, the orange reducer will not work.
The nesting need the same as I described above and orange reducer must work on action
Related
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]);.
I have an application that uers lets create new tables, lets say I have the following state:
const initialState = {
table: [{
id: 1,
item: {}
}]
}
Now I want to add new objects to the item:
The reducer:
case types.ADD_ITEM:
return {
...state,
table: [...state.table, { item: action.item}],
loading: false
}
So after user adds new items in a table the state should look something like this:
table: [
// table with id 1
{
id: 1,
item: {
id: '1',
data: 'etc'
}
item: {
id: '2',
data: 'blabla'
}
},
// table with id 2
{
id: 2,
item: {
id: '1'
data: 'etc'
}
},
// etc
]
I think I have my reducer wrongly setup.. the tables are added correctly, but Items not.
Current format is:
table: [
{
id: 1,
item: []
},
{
item: {
id: '1'
}
}
]
I think you are trying to add the contents of action.item to your array. What you're currently doing is adding the contents as a new element. You can use the following to achieve your desired output:
case types.ADD_ITEM:
return {
...state,
table: [...state.table, { ...action.item }],
loading: false
}
Or if you want to get only the id field of the item, and store the item inside a item field:
case types.ADD_ITEM:
return {
...state,
table: [...state.table, { id: action.item.id, item: action.item }],
loading: false
}
I have a rails backend with the following relationships: a USER has many MOVES. a Move has many boxes. A Box has many items.
I have page that lists all of the boxes inside of a specific move and this page ALSO lists all of the items for that specific move. I have a search bar on this page that enables you to search for specific items. I am able to filter my items display, however, i cannot figure out how to filter my boxes BY the searching for the name of the items WITHIN them.
I have tried iterating over the array of Box objects, and then iterating over the key within each box that points to its array of items. I am able to get the filtered ITEMS, but I dont know how to translate that back to reflect the BOXES with those items.
For instance, in the console I tried:
var filteredBoxes = boxes.map((box) => {
return box.items.filter((i) => {
return i.name.includes(this.state.searchTerm)
})
})
But it keeps returning items, not the boxes im trying to filter.
This is how the JSON looks when I fetch my boxes. I used a serializer to list the items as well:
{
id: 1,
name: "Bedding",
category: "Bedroom",
move_id: 1,
move: {
id: 1,
name: "Leaving for College",
date: "2019-08-12",
user_id: 1
},
items: [
{
id: 1,
name: "Comforter",
image: "https://www.shopmarriott.com/images/products/v2/lrg/Marriott-down-duvet-comforter-MAR-112_1_lrg.jpg",
box_id: 1
},
{
id: 2,
name: "Throw Pillows",
image: "https://media.kohlsimg.com/is/image/kohls/3427815?wid=500&hei=500&op_sharpen=1",
box_id: 1
}
]
},
{
id: 2,
name: "Random Blankets",
category: "Den",
move_id: 1,
move: {
id: 1,
name: "Leaving for College",
date: "2019-08-12",
user_id: 1
},
items: [
{
id: 3,
name: "Pillows",
image: "https://www.greatsleep.com/on/demandware.static/-/Sites-tbp-master-catalog/default/dw9ff5c1cf/product-images/pillows/nautica/down-alt-pillow-2-pack-na-91644/nautica-down-alternative-pillow-2-pack_91644-icon-2500x2500.jpg",
box_id: 2
},
{
id: 4,
name: "Stuffed Animals",
image: "https://s7d9.scene7.com/is/image/JCPenney/DP0817201617082870M?resmode=sharp2&op_sharpen=1&wid=550&hei=550",
box_id: 2
}
]
},
{
id: 3,
name: "Cleaning Supplies",
category: "Kitchen",
move_id: 1,
move: {
id: 1,
name: "Leaving for College",
date: "2019-08-12",
user_id: 1
},
items: [
{
id: 5,
name: "Pillows",
image: "https://www.greatsleep.com/on/demandware.static/-/Sites-tbp-master-catalog/default/dw9ff5c1cf/product-images/pillows/nautica/down-alt-pillow-2-pack-na-91644/nautica-down-alternative-pillow-2-pack_91644-icon-2500x2500.jpg",
box_id: 3
},
{
id: 6,
name: "Stuffed Animals",
image: "https://s7d9.scene7.com/is/image/JCPenney/DP0817201617082870M?resmode=sharp2&op_sharpen=1&wid=550&hei=550",
box_id: 3
}
]
}
you just have to iterate boxes, and so filter items. Based on these filtered items you may choose to return or not a box to the list.
const data = [{
id:1,
name:"Bedding",
category:"Bedroom",
move_id:1,
move:{
id:1,
name:"Leaving for College",
date:"2019-08-12",
user_id:1
},
items:[
{
id:1,
name:"Comforter",
image:"https://www.shopmarriott.com/images/products/v2/lrg/Marriott-down-duvet-comforter-MAR-112_1_lrg.jpg",
box_id:1
},
{
id:2,
name:"Throw Pillows",
image:"https://media.kohlsimg.com/is/image/kohls/3427815?wid=500&hei=500&op_sharpen=1",
box_id:1
}
]
},
{
id:2,
name:"Random Blankets",
category:"Den",
move_id:1,
move:{
id:1,
name:"Leaving for College",
date:"2019-08-12",
user_id:1
},
items:[
{
id:3,
name:"Pillows",
image:"https://www.greatsleep.com/on/demandware.static/-/Sites-tbp-master-catalog/default/dw9ff5c1cf/product-images/pillows/nautica/down-alt-pillow-2-pack-na-91644/nautica-down-alternative-pillow-2-pack_91644-icon-2500x2500.jpg",
box_id:2
},
{
id:4,
name:"Stuffed Animals",
image:"https://s7d9.scene7.com/is/image/JCPenney/DP0817201617082870M?resmode=sharp2&op_sharpen=1&wid=550&hei=550",
box_id:2
}
]
},
{
id:3,
name:"Cleaning Supplies",
category:"Kitchen",
move_id:1,
move:{
id:1,
name:"Leaving for College",
date:"2019-08-12",
user_id:1
},
items:[
{
id:5,
name:"Pillows",
image:"https://www.greatsleep.com/on/demandware.static/-/Sites-tbp-master-catalog/default/dw9ff5c1cf/product-images/pillows/nautica/down-alt-pillow-2-pack-na-91644/nautica-down-alternative-pillow-2-pack_91644-icon-2500x2500.jpg",
box_id:3
},
{
id:6,
name:"Stuffed Animals",
image:"https://s7d9.scene7.com/is/image/JCPenney/DP0817201617082870M?resmode=sharp2&op_sharpen=1&wid=550&hei=550",
box_id:3
}
]
}];
const searchTerm = "Animals"
// function to filter sub-items
const filterItems = items => items.filter((i) => searchTerm ? i.name.includes(searchTerm) : i.name);
const filteredBoxes = data.map(boxes => {
//filter sub-items
const items = filterItems(boxes.items);
//in case there is any item, return that boxes
if (items.length) {
return Object.assign({}, boxes, { items })
}
// in case there is nothing, return false
return false;
}).filter(Boolean); // filter the boxes list removing the false values
console.log('filteredBoxes', filteredBoxes);
Prior to this, I was managing general Redux state as follows:
for example I was setting isRequestTags from a reducer.
But now I'm facing another challenge:
Suppose I have a list of tags, for each tag there can be some states defined like isPrimaryTag.
How can I define states for a list of items which have a common attribute?
If you have a list of tags, and each tag has, say, a name and a flag, then you can't "refactor" that out in any meaningful way, e.g.,
tags: [
{ name: 'foo', isPrimary: true },
{ name: 'bar', isPrimary: false }
]
If the common attributes are themselves an object, particularly a large one, you'd use normal state-shape practices as outlined in the Redux docs.
For example, if each tag had something like this:
tagInfo: {
isPrimary: true,
group: 'whatever',
somethingElse: { etc: 'etc' }
}
and multiple tags had the same value, you'd provide an ID/index:
tagInfos: [
{
isPrimary: true,
group: 'whatever',
somethingElse: { etc: 'etc' }
},
{
isPrimary: true,
group: 'whatever',
somethingElse: { etc: 'etc' }
}
]
tags: [
{ name: 'foo', tagInfoIndex: 0 },
{ name: 'bar', tagInfoIndex: 1 }
// etc
]
All that said, I'm not entirely sure if that's what you're asking.
I'm reading the Redux Reducers docs and don't get how normalizing the state would work. The current state in the example is this:
{
visibilityFilter: 'SHOW_ALL',
todos: [
{
text: 'Consider using Redux',
completed: true,
},
{
text: 'Keep all state in a single tree',
completed: false
}
]
}
Can you provide an example of what the above would look like if we followed the below?
For
example, keeping todosById: { id -> todo } and todos: array inside
the state would be a better idea in a real app, but we’re keeping the
example simple.
This example is straight from Normalizr.
[{
id: 1,
title: 'Some Article',
author: {
id: 1,
name: 'Dan'
}
}, {
id: 2,
title: 'Other Article',
author: {
id: 1,
name: 'Dan'
}
}]
Can be normalized this way-
{
result: [1, 2],
entities: {
articles: {
1: {
id: 1,
title: 'Some Article',
author: 1
},
2: {
id: 2,
title: 'Other Article',
author: 1
}
},
users: {
1: {
id: 1,
name: 'Dan'
}
}
}
}
What's the advantage of normalization?
You get to extract the exact part of your state tree that you want.
For instance- You have an array of objects containing information about the articles. If you want to select a particular object from that array, you'll have to iterate through entire array. Worst case is that the desired object is not present in the array. To overcome this, we normalize the data.
To normalize the data, store the unique identifiers of each object in a separate array. Let's call that array as results.
result: [1, 2, 3 ..]
And transform the array of objects into an object with keys as the id(See the second snippet). Call that object as entities.
Ultimately, to access the object with id 1, simply do this- entities.articles["1"].
You can use normalizr for this.
Normalizr takes JSON and a schema and replaces nested entities with their IDs, gathering all entities in dictionaries.
For example,
[{
id: 1,
title: 'Some Article',
author: {
id: 1,
name: 'Dan'
}
}, {
id: 2,
title: 'Other Article',
author: {
id: 1,
name: 'Dan'
}
}]
can be normalized to
{
result: [1, 2],
entities: {
articles: {
1: {
id: 1,
title: 'Some Article',
author: 1
},
2: {
id: 2,
title: 'Other Article',
author: 1
}
},
users: {
1: {
id: 1,
name: 'Dan'
}
}
}
}