Deleting/Updating nested object using React Hooks - reactjs

I am relatively new to react-native programming language. I've been trying to make my first standalone application, which of course is To Do List app.
I have to do items under their own respective categories. The source of these to do items is a predefined object as shown below.
const [listData, setListData] = useState([
{
id: 1,
title: "Work",
data: [
{
text: 'MSD',
key: 1
},
{
text: 'Cosmo',
key: 2
},
{
text: 'Jackson',
key: 3
},
]
},
{
id: 2,
title: "Home",
data: [
{
text: 'Gym',
key: 4
},
{
text: 'Dinner',
key: 5
},
{
text: 'React',
key: 6
},
]
},
]);
This data is defined inside the main function App(). The data renders perfectly on screen with the below code.
Home Screen Image
return(
<View style={styles.container}>
<Header />
<SectionList
sections={listData}
keyExtractor={(item, index) => item + index}
renderSectionHeader={({
section: { title } }) => (
<Text style={styles.header}>{title}</Text>
)}
renderItem={({ item }) => <Item data={item.text} dataID={item.key} onDeleteHandler={onDeleteHandler}/>}
/>
</View>
)
Item component code is -
const Item = ({ data, dataID, onDeleteHandler }) => {
const [checked, setChecked] = React.useState(false);
return(
<View style={styles.itemGroup}>
<TouchableOpacity style={styles.item} onPress={() => {
if (checked == true){
setChecked(false)
}
else{
setChecked(true)
}
}}>
<View>
<Text style={checked === true ? styles.toDoTextStrikeThrough : styles.toDoText}>{data}</Text>
</View>
</TouchableOpacity>
<TouchableOpacity onPress={() => onDeleteHandler(dataID)}>
<View style={styles.checkBoxAndDeleteStyle}>
<Entypo name="cross" color='#004d4d' size={18}/>
</View>
</TouchableOpacity>
</View>
)
};
export default Item;
The bit where i'm stuck at is the delete button which invokes the below function.
const onDeleteHandler = (key) => {
const parentArray = [...listData]
for (let i = 0; i < parentArray.length; i++)
{
let paDataItems = {...parentArray[i]}.data
const dataArray = [...paDataItems]
for (let k = 0; k < dataArray.length; k++){
if (dataArray[k].key === key){
dataArray.splice(k,1)
break
}
}
console.log(dataArray)
}
}
dataArray shows all the correct objects. I am not able to figure out how to update this back to the main dataset.
I've tried the below two methods and both result in weird behaviours.
setListData((paDataItems) => [...paDataItems,{data:dataArray}])
setListData([{...parentArray[i]}, {data:dataArray}])
How should I write this function so I can delete the selected item?
I'm guessing I should be able to figure out how to update the dataset once I have a solution to the above problem.
Any help would be appreciated. Thank you.

You can map the groups/categories and filter the data array:
const onDeleteHandler = key => {
const newData = listData.map(category => ({
...category,
data: category.data.filter(item => item.key !== key)
})
);
setListData(newData);
};
It may be more correct to use a functional state update since the next state depends on the previous state having an task item deleted correctly in the case that multiple state updates are queued up within a single render cycle:
const onDeleteHandler = key => {
setListData(listData => listData.map(category => ({
...category,
data: category.data.filter(item => item.key !== key)
})
));
};
let data = [{
id: 1,
title: "Work",
data: [{
text: 'MSD',
key: 1
},
{
text: 'Cosmo',
key: 2
},
{
text: 'Jackson',
key: 3
},
]
},
{
id: 2,
title: "Home",
data: [{
text: 'Gym',
key: 4
},
{
text: 'Dinner',
key: 5
},
{
text: 'React',
key: 6
},
]
},
];
const onDeleteHandler = key => {
const newData = data.map(category => ({
...category,
data: category.data.filter(item => item.key !== key)
})
);
return newData;
};
console.log(onDeleteHandler(3))

You can simplify your onDeleteHandler function like this:
const newData = listData.map(group => {
return {
...group,
data: group.data.filter(task => task.key !== key)
}
})
setListData(newData);

Related

Ant design Transform component extracting selected data

I need to extract selected/filtered data into the state. I tried everything with onChange and on select change but only I got is the ID of one specific item.
ex. extracted_data = [ '1','2','3'....]
But I need a complete Item with id, name, price, etc...
I am passing data as source data, and after selecting I want to send in the parent component because I need to send it on the server.
Code is below
import { Table, Transfer } from "antd";
import difference from "lodash/difference";
import React, { useState } from "react";
// Customize Table Transfer
const TableTransfer = ({ leftColumns, rightColumns, ...restProps }) => (
<Transfer {...restProps}>
{({
direction,
filteredItems,
onItemSelectAll,
onItemSelect,
selectedKeys: listSelectedKeys,
disabled: listDisabled,
}) => {
const columns = direction === "left" ? leftColumns : rightColumns;
const rowSelection = {
getCheckboxProps: (item) => ({
disabled: listDisabled || item.disabled,
}),
onSelectAll(selected, selectedRows) {
const treeSelectedKeys = selectedRows
.filter((item) => !item.disabled)
.map(({ key }) => key);
const diffKeys = selected
? difference(treeSelectedKeys, listSelectedKeys)
: difference(listSelectedKeys, treeSelectedKeys);
onItemSelectAll(diffKeys, selected);
},
onSelect({ key }, selected) {
onItemSelect(key, selected);
},
selectedRowKeys: listSelectedKeys,
};
return (
<Table
rowSelection={rowSelection}
columns={columns}
dataSource={filteredItems}
size="small"
style={{
pointerEvents: listDisabled ? "none" : undefined,
}}
onRow={({ key, disabled: itemDisabled }) => ({
onClick: () => {
if (itemDisabled || listDisabled) return;
onItemSelect(key, !listSelectedKeys.includes(key));
},
})}
/>
);
}}
</Transfer>
);
const leftTableColumns = [
{
dataIndex: "name",
title: "Name",
},
{
dataIndex: "price",
title: "Price (€)",
},
{
dataIndex: "discount",
title: "Discount (%)",
},
];
const rightTableColumns = [
{
dataIndex: "name",
title: "Name",
},
{
dataIndex: "price",
title: "Price",
},
];
const App = ({ data, func }) => {
const mockData = data.map((item) => ({
key: item.id.toString(),
name: item.name,
price: item.price,
discount: item.discount,
}));
const originTargetKeys = mockData.map((item) => item.key);
const [targetKeys, setTargetKeys] = useState(originTargetKeys);
const [selected, setSelected] = useState([]);
const onSelectChange = (e) => {
setSelected(e);
};
const onChange = (e) => {
setTargetKeys(e);
func(selected);
};
return (
<>
<TableTransfer
dataSource={mockData}
targetKeys={targetKeys}
disabled={false}
showSearch={true}
onChange={onChange}
onSelectChange={onSelectChange}
filterOption={(inputValue, item) =>
item.name.indexOf(inputValue) !== -1
}
leftColumns={leftTableColumns}
rightColumns={rightTableColumns}
/>
</>
);
};
export default App;

React Card Example issue - Card is replaced instead of being appended to a list of cards in another column

I have been persistently working on this problem where the goal is to drag a card form 'Column 1' and copy that into another column say 'Column 2'.
Now when my first card is dragged and drop it into 'Column 2, the card is accordingly added to that column, but when I drag another card and drop into 'Column 2' instead of being appended it just replaces the existing card with itself.
I have been debugging the state, but the issue still persists. I haven't gotten a clue what am I doing wrong here?
Here's my code
// Card Component
function Card({ id, text, isDrag }) {
const [, drag] = useDrag(() => ({
type: "bp-card",
item: () => {
return { id, text}
},
collect: monitor => ({
isDragging: !!monitor.isDragging(),
}),
canDrag: () => isDrag
}));
return (
<div
className='card'
ref={drag}
style={{
cursor: isDrag ? 'pointer' : 'no-drop'
}}
>
{text}
</div>
)
}
// Column Component
function Column({ title, children, onCardDropped }) {
const [, drop] = useDrop(() => ({
accept: "bp-card",
drop: item => {
onCardDropped(item);
}
}));
return (
<div className="flex-item" ref={title === 'Column 2' ? drop : null}>
<p>{title}</p>
{children.length > 0 && children.map(({ id, text, isDrag }) => (
<Card
key={id}
id={id}
text={text}
isDrag={isDrag}
/>
))}
</div>
)
}
// Main App
function App() {
const [cards] = useState([
{ id: 1, text: 'Card 1', isDrag: true },
{ id: 2, text: 'Card 2', isDrag: true },
]);
const [columns, setColumns] = useState([
{
id: 1,
title: 'Column 1',
children: cards
},
{
id: 2,
title: 'Column 2',
children: []
},
]);
const onCardDropped = ({ id, text }) => {
// let card = null;
const targetColumnId = 2;
const transformedColumns = columns.map(column => {
if (column.id === targetColumnId) {
return {
...column,
children: [
...column.children,
{ id, text }
]
}
}
return column;
});
setColumns(transformedColumns);
}
return (
<DndProvider backend={HTML5Backend}>
<div className='flex-container'>
{columns.map((column) => (
<Column
key={column.id}
title={column.title}
children={column.children}
onCardDropped={onCardDropped}
/>
))}
</div>
</DndProvider>
);
}
Any help is highly appreciated. Thanks.
You need to consider the previous state using the callback of the set state method. It starts to work after changing the onCardDropped as below.
const onCardDropped = ({ id, text }) => {
// let card = null;
const targetColumnId = 2;
setColumns((prevColumns) =>
prevColumns.map((column) => {
if (column.id === targetColumnId) {
return {
...column,
children: [...column.children, { id, text }]
};
}
return column;
})
);
};
It's always a good idea to use the state from the callback method as opposed to using the state object directly which might be stale.
Working Demo

React Native screen does not update when object state changes

I am using react-native to build my app.
The main screen stores an object in the state:
const [menu, setMenu] = React.useState({name: "Pizza", toppings: {chicken: 2}})
and I display this state to the user as:
<Text>Qty: {menu.toppings.chicken}</Text>
I have a counter to update the state the loaginc to update the state is:
const handleChange = (action: string) => {
if (action === 'add') {
setMenu((prev) => {
prev.toppings.chicken += 1
return prev
})
} else if (action === 'subtract') {
setMenu((prev) => {
prev.calendar.booking -= 1
return prev
})
}
}
My function works correctly and changes the state to according to my needs(verified by console logging). However these changes are not reflexted in the <Text> where I am showing the quantity.
You should research more about Shallow compare:
How does shallow compare work in react
In your case, you can try this code:
const handleChange = (action: string) => {
if (action === 'add') {
setMenu((prev: any) => {
prev.toppings.chicken += 1;
return { ...prev };
});
} else if (action === 'subtract') {
setMenu((prev: any) => {
prev.calendar.booking -= 1;
return {...prev};
});
}
};
This is your solution, it gets complicated with nested objects :
const handleChange = () => {
setMenu((prevState) => ({
...prevState,
toppings: { chicken: prevState.toppings.chicken + 1 }
}));
};
Hope This Solution Help:
import React from 'react';
import {View, Text, TouchableOpacity} from 'react-native';
export default App = () => {
const [menu, setMenu] = React.useState({
name: 'Pizza',
toppings: {
value: 2,
},
});
React.useEffect(() => {
console.log('menu', menu);
}, [menu]);
const handleIncrement = () => {
setMenu(prev => ({
...prev,
toppings: {...prev.toppings, value: prev.toppings.value + 1},
}));
};
const handleDecrement = () => {
setMenu(prev => ({
...prev,
toppings: {...prev.toppings, value: prev.toppings.value - 1},
}));
};
return (
<View style={{flex: 1}}>
<View
style={{
alignItems: 'center',
flex: 1,
flexDirection: 'row',
justifyContent: 'space-evenly',
}}>
<TouchableOpacity onPress={handleDecrement}>
<Text style={{fontSize: 44}}>-</Text>
</TouchableOpacity>
<Text>{menu.toppings.value}</Text>
<TouchableOpacity onPress={handleIncrement}>
<Text style={{fontSize: 44}}>+</Text>
</TouchableOpacity>
</View>
</View>
);
};

FlatList update wrong element

I have update my flatlist according to last received messages that is rendered componenet of Flatlist. For that reason i coded below code but this code always update last item of flatlist instead of its key.
const [lastMessage, setlastMessage] = useState([
{ key: "1", text: "j1" },
{ key: "2", text: "j2" },
{ key: "3", text: "j3" },
]);
const goBack = (id, data) => { // Here is the change statement that update flatlist.
let result = lastMessage.filter((obj) => {
return obj.key != id;
});
result.push({ key: id, text: data });
setlastMessage(result);
};
Flatlist:
<FlatList
contentContainerStyle={{
width: "100%",
paddingBottom: 200,
}}
keyExtractor={(item) => item.key}
data={lastMessage}
extraData={lastMessage}
renderItem={({ item }) => {
return (
<TouchableOpacity
activeOpacity={0.55}
onPress={() =>
navigation.navigate("ChatScreen", {
ChatRoomID: item.key,
onGoback: goBack, // Here is the reference of function
})
}
>
<ChatCard text={item.text} />
</TouchableOpacity>
);
}}
/>
Chat Screen:
async function handleSend(messages) {
const writes = messages.map((m) => {
chatsRef.add(m);
goBack(ChatRoomID, m["text"]); // i give value to parent from that
});
await Promise.all(writes);
}
Use Array.map instead of filter.
const goBack = (id, data) => {
// Here is the change statement that update flatlist.
let result = lastMessage.map(obj =>
obj.key === id ? { key: id, text: data } : obj
);
setlastMessage(result);
};
try this:
const goBack = (id, data) => {
setlastMessage((value) => {
let result = value.filter((obj) => {
return obj.key != id;
});
return [...result, { key: id, text: data }];
});
};

Material-Table with React: how to use star rating in the cell?

I would like to style my cell's rating into star by using Material-Table,
like the original Material-UI provided:
https://material-ui.com/components/rating/
Is it possible to use in Material-Table? I cannot find document related to this...just for the style for background, color, etc., not for writing functions in cell style.
https://material-table.com/#/docs/features/styling
thanks a lot!
You can use material-table's custom edit component to render the mui Rating component.
Full Working demo
Sample code snippet of columns array
const columns = propValue => [
{ title: "Id", field: "id" },
{ title: "First Name", field: "first_name" },
{
title: "Rating",
field: "rating",
render: rowData => {
return <Rating name="hover-feedback" value={rowData.rating} readOnly />;
},
editComponent: props => (
<Rating
name="hover-feedback"
value={props.value}
onChange={(event, newValue) => {
props.onChange(newValue);
}}
/>
),
cellStyle: {
backgroundColor: "#039be5",
color: "#FFF"
},
width: "30%"
}
];
Component
class App extends Component {
tableRef = React.createRef();
propValue = true;
state = { data: [] };
componentDidMount() {
const query = 0;
let url = "https://reqres.in/api/users?";
url += "per_page=" + query.pageSize;
url += "&page=" + (query.page + 1);
fetch(url)
.then(response => response.json())
.then(result => {
console.log("result", result);
this.setState({
data: result.data.map(d => ({ ...d }))
});
});
}
render() {
return (
<div style={{ maxWidth: "100%" }}>
<MaterialTable
icons={tableIcons}
tableRef={this.tableRef}
columns={columns(this.propValue)}
editable={{
onRowUpdate: (newData, oldData) =>
new Promise((resolve, reject) => {
console.log("newData", newData);
console.log("oldData", oldData);
const dataUpdate = [...this.state.data];
const index = oldData.tableData.id;
dataUpdate[index] = newData;
this.setState({ data: dataUpdate }, () => {
console.log("xx", this.state.data);
resolve(this.state);
});
})
}}
data={this.state.data}
title="Remote Data Example"
options={{ tableLayout: "fixed" }}
/>
<button
onClick={() => {
this.tableRef.current.onQueryChange();
}}
>
ok
</button>
</div>
);
}
}

Resources