My state has an array of objects that also contains an array of objects.
var state = {
prop1: null,
categories: [
{
categoryId: 1,
tags: [
{
tagId: 1,
name: 'AA11',
status: true,
},
{
tagId: 2,
name: 'AA22',
status: false,
}
]
},
{
categoryId: 2,
tags: [
{
tagId: 1,
name: 'BB11',
status: true,
},
{
tagId: 2,
name: 'BB22',
status: false, // let's say i want to toggle this
}
]
},
]
};
I have an action that will toggle a status of a tag. This action will receive parameters categoryId and tagId.
So far I've come up with this but it doesn't work
return {
...state,
categories: state.categories.map((category) => {
category.tags.map(tag => (
(tag.tagId === action.tagId && category.categoryId === action.categoryId) ? {
...tag,
status: !tag.status,
} : tag));
return category;
}),
};
I finally fixed the map code.
return {
...state,
categories: state.categories.map(category => ((category.id === action.categoryId) ?
{
...category,
tags: category.tags.map(tag => (tag.id === action.tagId ? {
...tag, status: !tag.status,
} : tag)),
} : category)),
};
Related
I have a Flatlist of about 20 filters.
Each filter selector is a Pressable.
when either filters with id 11 or 12 is checked, filter id 14 needs to become disabled.
If filter 14 is checked, both filters 11 & 12 need to become disabled.
I am handling the state with redux.
Summary - The function is doing what it is supposed to do and the Redux state is being updated correctly. The Flatlist is getting re-rendered but the disabled state on the UI doesn't change.
This is the filters arrays -
export const area = [
{ id: 1, txt: "West shore South", isChecked: false, isDisabled: false },
{ id: 2, txt: "West shore North", isChecked: false, isDisabled: false },
{ id: 3, txt: "North shore", isChecked: false, isDisabled: false },
{ id: 4, txt: "East shore", isChecked: false, isDisabled: false },
{ id: 5, txt: "South shore", isChecked: false, isDisabled: false },
{ id: 6, txt: "Barbuda", isChecked: false, isDisabled: false },
{ id: 7, txt: "Outer Islands", isChecked: false, isDisabled: false },
];
export const access = [
{ id: 11, txt: "Car", isChecked: false, isDisabled: false },
{ id: 12, txt: "Hike", isChecked: false, isDisabled: false },
{ id: 13, txt: "Public", isChecked: false, isDisabled: false },
{ id: 14, txt: "Boat Only", isChecked: false, isDisabled: false },
];
(There are couple of extra filter arrays that are irrelevant to this question).
This is the Redux slice that handles the state -
import { createSlice } from "#reduxjs/toolkit";
import { area, access, nature, other } from "../logic/FiltersData";
export const activeFiltersSlice = createSlice({
name: "activeFilters",
initialState: {
filters: [...area, ...access, ...other, ...nature],
active: [],
},
reducers: {
updateAllFilters: (state, action) => {
state.filters = action.payload;
state.active = state.filters.filter((each) => each.isChecked);
},
},
});
export const { updateAllFilters } = activeFiltersSlice.actions;
export default activeFiltersSlice.reducer;
This is the function I used to handle the state before trying to disable buttons (products being the array of all the filters)
const handleChange = (id) => {
let temp = products.map((product) => {
if (id === product.id) {
return { ...product, isChecked: !product.isChecked };
}
return product;
});
dispatch(updateAllFilters(temp));
};
This is the same function after I added the disabled change of state.
const handleChange = (id) => {
const car = products.findIndex((item) => item.id === 11);
const hike = products.findIndex((item) => item.id === 12);
let temp = products.map((product) => {
if (id === 11 || id === 12) {
if (product.id === 14) {
if (
(id === 11 &&
products[car].isChecked &&
!products[hike].isChecked) ||
(id === 12 && !products[car].isChecked && products[hike].isChecked)
) {
return {
...product,
isDisabled: false,
};
} else {
return {
...product,
isDisabled: true,
};
}
} else if (id === product.id) {
return { ...product, isChecked: !product.isChecked };
}
}
if (id === 14) {
if (product.id === 11 || product.id === 12) {
return {
...product,
isDisabled: !product.isDisabled,
};
} else if (id === product.id) {
return { ...product, isChecked: !product.isChecked };
}
}
if (id === product.id) {
return { ...product, isChecked: !product.isChecked };
}
return product;
});
dispatch(updateAllFilters(temp));
};
This is how I render the Flatlist (Section being one array of filters)
const renderFlatList = (section) => {
const print = section.map((item, index) => {
const found = products.find(
(element) => element.id === item.id
).isChecked;
return (
<Pressable
key={item.id}
style={found ? styles.itemChecked : styles.item}
hitSlop={5}
onPress={() => handleChange(item.id)}
disabled={item.isDisabled}
>
<View style={styles.itemText}>
<Text style={found ? styles.txtChecked : styles.txt}>
{item.txt}
</Text>
</View>
<View style={styles.icon}>
<MaterialCommunityIcons
name={
found
? "checkbox-multiple-marked-circle"
: "checkbox-multiple-blank-circle-outline"
}
size={32}
color={iconColor}
/>
</View>
</Pressable>
);
});
return print;
};
I use this code to verify the Redux state is updating correctly after each click, which it does
products.map((each) => {
if (each.id > 10 && each.id < 20) {
console.log(each);
}
});
However, the filters that showing on the Redux as isDisabled: true are not becoming disabled!
Why?
Thanks
And a secondary question - if anyone have an idea how to write the handleChange() function cleaner and more efficient, that will be lovely.
I have a JSON array, which looks as follows.
[
{
id: 1,
name: 'Alex',
activity: [
{
id: 'A1',
status: true
},
{
id: 'A2',
status: true
},
{
id: 'A3',
status: false
}
]
},
{
id: 2,
name: 'John',
activity: [
{
id: 'A6',
status: true
},
{
id: 'A8',
status: false
},
{
id: 'A7',
status: false
}
]
}
]
I want to get an array of activity id whose status should be true.I can achieve this with nester for or forEach loop. But here I am looking to achieve with the help of array functions like filter, map, and some.
I have already tried with the following.
let newArr=arr.filter(a=> a.activity.filter(b=> b.status).map(c=> c.id))
But I didn't get the correct answer
Expected output
['A1','A2','A6']
function filter_activity(activities) {
return activities
&& activities.length
&& activities.map(x => x.activity)
.flat().filter(activity => activity.status)
.map(x => x.id) || [];
}
Illustration
function filter_activity(activities) {
return activities &&
activities.length &&
activities.map(x => x.activity)
.flat().filter(activity => activity.status)
.map(x => x.id) || [];
}
const input = [{
id: 1,
name: 'Alex',
activity: [{
id: 'A1',
status: true
},
{
id: 'A2',
status: true
},
{
id: 'A3',
status: false
}
]
},
{
id: 2,
name: 'John',
activity: [{
id: 'A6',
status: true
},
{
id: 'A8',
status: false
},
{
id: 'A7',
status: false
}
]
}
];
console.log(filter_activity(input));
WYSIWYG => WHAT YOU SHOW IS WHAT YOU GET
let arr = json.flatMap(e => e.activity.filter(el => el.status).map(el => el.id))
let newArr=arr.map(x => x.activity)
.reduce((acc, val) => acc.concat(val), [])
.filter((activity:any) => activity.status)
.map((x:any) => x.id) || [];
I got error when using flat() and flatMap().So, I have used reduce().
I need some input from people more experienced with Zustand to share their way of managing relational state. Currently we have the following:
Let's assume we have the example entities Campaign, Elementss and their Settings. The REST API returning them is in the following format:
GET <API>/campaigns/1?incl=elements,elements.settings
{
"id":1,
"name":"Welcome Campaign",
"elements":[
{
"id":5,
"type":"heading",
"label":"Heading",
"content":"Welcome!",
"settings":[
{
"id":14,
"name":"backgroundColor",
"value":"#ffffff00"
},
{
"id":15,
"name":"color",
"value":"#ffffff00"
}
]
},
{
"id":6,
"type":"button",
"label":"Button",
"content":"Submit",
"settings":[
{
"id":16,
"name":"backgroundColor",
"value":"#ffffff00"
},
{
"id":17,
"name":"color",
"value":"#ffffff00"
},
{
"id":18,
"name":"borderRadius",
"value":"3px"
}
...
]
}
...
]
}
What we are currently doing in the Reactjs app is fetching this data, then transforming it to the following normalized format and set functions:
const useCurrentCampaignStore = create(
combine(
{
campaign: {
id: 1,
name: "Welcome Campaign"
},
elements: [
{
id: 5,
campaignId: 1,
type: "heading",
label: "Heading",
content: "Welcome!"
},
{
id: 6,
campaignId: 1,
type: "button",
label: "Button",
content: "Submit"
}
],
settings: [
{
id: 14,
elementId: 5,
name: "backgroundColor",
value: "#ffffff00"
},
{
id: 15,
elementId: 5,
name: "color",
value: "#ffffff00"
},
{
id: 16,
elementId: 6,
name: "backgroundColor",
value: "#ffffff00"
},
{
id: 17,
elementId: 6,
name: "disabled",
value: false
},
{
id: 18,
elementId: 6,
name: "borderRadius",
value: 3,
}
]
},
set => ({
updateSetting: (id: string | number, newValue: string | number | boolean) =>
set(state => {
const settings = [...state.settings];
return {
...state,
settings: settings.map(setting => {
if (setting.id == id) {
return { ...setting, value: newValue };
}
return setting;
})
};
}),
updateElementContent: (id: string | number, newValue: string) => {
set(state => {
const elements = [...state.elements];
return {
...state,
elements: elements.map(element => {
if (element.id == id) {
return { ...element, content: newValue };
}
return element;
})
};
});
}
})
)
);
I am, however, not sure this is the optimal solution, because It's rather tedious transforming all GET requests to a normalized format and then converting them back to nested objects when you want to construct either a POST, PUT or PATCH request.
So, to put it short, how do you guys design the state in your Zustand-based RESTful-API-backed React apps?
I am trying to write assertion for a menu list which render after full fill the expression condition and sorted such as:
{packageFamilyId &&
packageMap[packageFamilyId] &&
packageMap[packageFamilyId].sort(Constants.sortAlphabeticallyAscending)
.map((p) => (
<MenuItem value={p.id} key={p.id} data-test="package-menu">{p.name}</MenuItem>
}
I am passing props as:
const props = {
packageFamilyId: 1,
packageMap: [
{
packageFamilyId: [],
},
{
packageFamilyId: [
{
active: true,
code: "0",
id: 121,
is_price_applicable: false,
name: "ctest_name",
typeId: 2,
},
{
active: true,
code: "0",
id: 121,
is_price_applicable: false,
name: "btest_name",
typeId: 2,
},
{
active: true,
code: "0",
id: 121,
is_price_applicable: false,
name: "atest_name",
typeId: 2,
}
],
},
],
};
import React from "react";
import { shallow } from "enzyme";
import { componentName } from "./componentName ";
import componentNameMock from "../../../test/mockData/componentName";
describe("componentNameComponent", () => {
const wrapper = shallow(<component {...props} />);
const menuList = wrapper.find("[data-test='package-menu']");
expect(menuList .length()).toEqual(1);
});
But it is giving me Type Error:
TypeError: packageMap[packageFamilyId].sort is not a function
packageMap[packageFamilyId] &&
packageMap[packageFamilyId]
.sort(Constants.sortAlphabeticallyAscending)
............................................................................
where Constants.sortAlphabeticallyAscending is sorting function
sortAlphabeticallyAscending: (a, b) => {
if (a !== undefined && b !== undefined) {
if (a.name?.toLowerCase() < b.name?.toLowerCase()) return -1;
else if (a.name?.toLowerCase() > b.name?.toLowerCase()) return 1;
else return 0;
}
}
Issue
packageMap[packageFamilyId] is an object:
{
packageFamilyId: [
{
active: true,
code: "0",
id: 121,
is_price_applicable: false,
name: "ctest_name",
typeId: 2,
},
{
active: true,
code: "0",
id: 121,
is_price_applicable: false,
name: "btest_name",
typeId: 2,
},
{
active: true,
code: "0",
id: 121,
is_price_applicable: false,
name: "atest_name",
typeId: 2,
}
],
}
This is not an array nor is there a sort property that is a function that can be called. In otherwords, packageMap[packageFamilyId].sort is undefined and not callable.
Solution
Access the property that is an array and sort this.
{packageMap[packageFamilyId]?.packageFamilyId?.sort(
Constants.sortAlphabeticallyAscending
).map((p) => (
<MenuItem value={p.id} key={p.id} data-test="package-menu">
{p.name}
</MenuItem>
)
)}
Since there are actually 3 elements in the specified array the test should be updated to assert against the correct length. length is also a property, not a function to call.
describe("componentNameComponent", () => {
const wrapper = shallow(<component {...props} />);
const menuList = wrapper.find("[data-test='package-menu']");
expect(menuList.length).toEqual(3);
});
it worked with mockup setting , instead of familyPackageId string i gave value 1(index value).
it("expects to render package-menu ", () => {
const wrapper = shallow(<componentNameComponent{...props} />);
const menuList = wrapper.find("[data-test='package-menu']");
expect(menuList .length).toEqual(props.packageMap["1"].length);
});
packageMap: {
1: [
{
id: 4,
isPriceApplicable: true,
is_price_applicable: true,
name: "ctest_name",
packageAgentType: "FoS",
typeId: 1,
},
{
id: 3,
isPriceApplicable: true,
is_price_applicable: true,
name: "btest_name",
packageAgentType: "FoS",
typeId: 1,
},
{
id: 4,
isPriceApplicable: true,
is_price_applicable: true,
name: "atest_name",
packageAgentType: "FoS",
typeId: 1,
},
],
},
I want to hide a button if there is atleast one order or subareas that at least one orders whose status 'ACCEPTED' or 'DONE'.
How can I hide the "Hide me" Menu item when either item has at least one area with order status 'ACCEPTED' OR 'DONE' or at least one area with subareas order with status 'ACCEPTED' or 'DONE'.
Below is the react code with the item I am trying to process
function Parent() {
const item = {
owner: {
id: '1',
cognitoId: '2',
},
areas: [{
id: '1',
orders: [
{
id: '1',
status: 'ASSIGNED',
}, {
id: '2',
status: 'ACCEPTED',
}
],
subAreas: [
{
id: '1',
orders: [
{
id: '4',
status: 'DONE',
}
],
}
]
}, {
id: '2',
orders: [{
id: '3',
status: 'ASSIGNED',
}, {
id: '4',
status: 'ACCEPTED',
}
],
subAreas: [{
id: '2',
orders: [{
id: '5',
status: 'DONE',
}, {
id: '6',
status: 'ACCEPTED',
}
],
}
]
}
]
}
return ({
item && item.owner && item.owner.cognitoId && (
< Menu > Hide me < / Menu > )
});
}
this Item is reference to how the data will look.
For additional information...Item is of type Item which is like below
export interface Item {
id: string;
name: string;
areas?: Area[];
}
export interface Area {
id: string;
Orders?: Order[];
subAreas?: Area[];
}
export interface Order {
id: string;
status: OrderStatus; //this is the one we are looping through
}
export interface OrderStatus {
NEW = 'NEW',
ASSIGNED = 'ASSIGNED',
SENT = 'SENT',
ACCEPTED = 'ACCEPTED',
REJECTED = 'REJECTED',
DONE = 'DONE',
}
what i have tried is like below
const hasDoneAccepted = () => {
return Object
.keys(item)
.some(key =>
(key === 'status' &&
['DONE', 'ACCEPTED'].indexOf(item[key]) > -1) ||
(typeof item[key] === 'object' &&
hasDoneAccepted(item[key])));
}
But this gives me an error like below,
Element implicitly has any type because expression of type "status" cant be used on index type 'Item'. property status doesnt exist on type 'Item'.
i am new to using typescript and not sure whats going wrong. could someone help me with this. thanks.
EDIT:
using the solution provided
const hasAcceptedOrDoneOrders =
item.areas?.reduce<Order[]>((acc, area) => { //error here
area.orders?.forEach(order => acc.push(order));
area.subAreas?.forEach(subArea => subArea.orders?.forEach(order =>
acc.push(order)));
return acc;
}, [])
.some(order => order.status === "ACCEPTED" || order.status === "DONE");
}
this gives me an here at line
item.areas?.reduce<Order[]>((acc, area) =>
"parsing error: expression expected"
looping through the object is always an option. I'd use a helper method too. Something like this:
hideBool = false;
for(cost area of item.areas()) {
if(hideBool) { break; }
hideBool = this.checkArray(area.orders);
if(!hideBool) {
for(const subArea of area.subAreas) {
hideBool = this.checkArray(subArea);
if(hideBool) { break; }
}
}
}
checkArray(array: any[]) {
for( const item in array) {
if(item.status === 'ACCEPTED' || item.status === 'DONE') {
return true;
}
}
return false;
}
This is how you can write the filter function:
const hasAcceptedOrDoneOrders =
item.areas?.reduce<Order[]>((acc, area) => {
area.orders?.forEach(order => acc.push(order));
area.subAreas?.forEach(subArea => subArea.orders?.forEach(order => acc.push(order)));
return acc;
}, [])
.some(order => order.status === "ACCEPTED" || order.status === "DONE");
and use it to conditional display React element:
return !hasAcceptedOrDoneOrders && <Menu>Hide me</Menu>;
However, to eliminate all the TypeScript syntax errors you need to correctly type the item object and fix the data schema:
const item: Item = {
id: "1",
name: "Item1",
owner: { id: "1", cognitoId: "2" },
areas: [
{
id: "1",
orders: [
{ id: "1", status: OrderStatus.ASSIGNED },
{ id: "2", status: OrderStatus.ACCEPTED }
],
subAreas: [
{
id: "1",
orders: [{ id: "4", status: OrderStatus.DONE }]
}
]
},
{
id: "2",
orders: [
{ id: "3", status: OrderStatus.ASSIGNED },
{ id: "4", status: OrderStatus.ACCEPTED }
],
subAreas: undefined
}
]
};
Schema:
interface Item {
id: string;
name: string;
areas?: Area[];
owner: any;
}
enum OrderStatus {
NEW = 'NEW',
ASSIGNED = 'ASSIGNED',
SENT = 'SENT',
ACCEPTED = 'ACCEPTED',
REJECTED = 'REJECTED',
DONE = 'DONE',
}