Ag-grid editable grid adding new row dynamically - javascript

I have an editable AgGrid in my functional component as below.
On the last column, I have buttons to Add/Remove rows.
Now I want the Add row to be displayed only for the last row. I am using cellRenderer for the last column.
With the below change, I get the Add button for the last row (i.e. on 2nd row in my case) on initial render. But if I click on Add for this 2nd row, while I get the Add button for the new 3rd row, but it does not get removed for the 2nd row. not sure if I am implementing this in the wrong way.
const MyCmp = (props) => {
const getData = () => {
return [{
id: 0,
firstName: 'F1',
lastName: 'L1'
}, {
id: 1,
firstName: 'F2',
lastName: 'L2',
}];
}
const [myCols, setMyCols] = useState(null);
const [gridData, setGridData] = useState(getData());
const [gridApi, setGridApi] = useState('');
let cellRules = {
'rag-red': params => {
if (params.data.lastName === 'INVALID_VAL') {
return true;
}
}
};
const handleGridReady = (params) => {
setGridApi(params.api);
setMyCols([{
headerName: 'F Name',
field: 'firstName',
editable: true
}, {
headerName: 'L Name',
field: 'lastName',
cellClassRules: cellRules,
editable: true
}, {
headerName: '',
field: 'buttonCol',
cellRenderer: 'customColRenderer',
cellRendererParams: {
addItems: addItems
}
}]
);
};
const createNewRowData = () => {
const newData = {
id: newCount,
firstName: '',
lastName: ''
};
newCount++;
return newData;
}
let newCount = getData().length;
const addItems = (addIndex, props) => {
const newItems = [createNewRowData()];
const res = props.api.applyTransaction({
add: newItems,
addIndex: addIndex,
});
setGridData(...gridData, res.add[0].data); // IS THIS CORRECT ?
if (props.api.getDisplayedRowCount() > props.api.paginationGetPageSize()) {
props.api.paginationGoToPage(parseInt((props.api.getDisplayedRowCount() / props.api.paginationGetPageSize())) + 1);
}
}
const onCellClicked = (e) => {
}
const frameworkComponents = () => {
return {customColRenderer: customColRenderer}
}
return (
<>
<MyAgGrid
id="myGrid"
columnDefs={myCols}
rowData={gridData}
frameworkComponents={{customColRenderer: customColRenderer}}
{...props}
/>
</>
)
}
My customColRenderer is as below;
export default (props) => {
let isLastRow = (props.rowIndex === (props.api.getDisplayedRowCount() -1)) ? true: false;
const addItems = (addIndex) => {
props.addItems(addIndex, props);
}
return (
<span>
{isLastRow ? <button onClick={() => addItems()}>Add</button> : null}
<span><button onClick={() => props.api.applyTransaction({remove: props.api.getSelectedRows()})}>Remove</button>
</span>
);
};

Within the AgGrid React internals a transaction is generated automatically when your rowData is updated, as such you can choose to apply the transaction either through the api, or by updating your state - you shouldn't need to do both (as you're currently doing). Generally with React I'd suggest updating the state to keep your state true to the data displayed in the grid, however that can depend on use case.
As for the further issue of your cell not updating - that'll be due to the fact AgGrid has detected no change in the 'value' of the cell, as it attempts to reduce the amount of unnecessary cell rendering done.
You could attempt to call:
api.refreshCells({ force: true });
After your api call applying the transaction (I'm not sure where this would need to happen using the setState approach).

Related

How to remove commas from strings in an array?

I need to remove commas which are in an array of odata objects. Each item has a description which sometimes contains commas. I need to remove those commas.
Here is what I've attempted:
Parent Component:
<CSVComponent capxs={allCaps} />
Child Component:
import { CSVLink } from "react-csv";
export interface ICSVComponentProps {
capxs: IListItem[];
}
export const CSVComponent: React.FunctionComponent<ICSVComponentProps> = (props: ICSVComponentProps) => {
const [results, setResults] = React.useState([]);
const headers = [
{ label: "Id", key: "id" },
{ label: "Title", key: "title" },
{ label: "Description", key: "description" },
];
const getCaps = (c: IListItem[]) => {
const results = [];
for (let i = 0; i <= c.length - 1; i++) {
results[i] = {
id: props.capxs[i].Id,
title: props.capxs[i].Title,
description: props.capxs[i].Description.replace(",",""),
};
}
setResults(results);
};
return (
<DefaultButton className={styles.csvButton}
onClick={() => getCaps(props.capxs)}
>
<CSVLink data={results} headers={headers}>
Create CSV
</CSVLink>
</DefaultButton>
);
Update:
I've updated the code sample above with the Parent component props pass down and more of the child component.
Here is an image showing the array that is created and stored in state. This state is then used by react-csv:
The description value in each item in the array has the comma removed, but react-csv seems to ignore this and it detects the commas, therefore creating incorrect columns in the produced csv file.
If I understand correctly what you need
const memoizedCaps = useMemo<IListItem[]>(() => {
return caps.map(el => ({ ...el, description: el.description.replace(',', '') }));
}, [caps]);
const getCaps = () => {
setResults(memoizedCaps);
};
This issue has been resolved by using:
const getCaps = (caps: IListItem[]) => {
const results = [];
for (let i = 0; i <= caps.length - 1; i++) {
results[i] = {
description: props.capxs[i].Description.replace(/,/g, '""'),
};
}
setResults(results);
};
Not ideal but it's a limitation of react-csv.
Ref: https://github.com/react-csv/react-csv/issues/176
Thanks for all your help.

How to select multiple rows and sub rows in React?

I'm looking for some help to implement row select functionality in my React app.
I have the following data that I provide to the table(react-data-table-component):
const data = useMemo(
() =>
[...Array(87)].map((_, outer) => ({
id: `o-${outer}`,
name: randFullName(),
age: randNumber({ max: 100 }),
price: randNumber({ max: 5000 }),
description: randProductDescription(),
active: randBoolean(),
date: randFutureDate().toLocaleString(),
items: [...Array(randNumber({ max: 10 }))].map((__, inner) => ({
id: `o-${outer}--i-${inner}`,
name: randFullName(),
age: randNumber({ max: 100 }),
price: randNumber({ max: 5000 }),
description: randProductDescription(),
active: randBoolean(),
date: randFutureDate().toLocaleString(),
})),
})),
[],
);
I display this data in a table.
There is a parent row and each parent row can be expanded which contains the child rows.
Now, I want to be able to select rows. I use a three-state checkbox where three states are:
checked: when all the child rows are checked
empty: when no children row is selected
intermediate: when some of the child rows are selected
I want to render the checkbox states and also be able to access the selected rows.
I am using a custom selector cell like this:
const SelectorCell = ({
rowId,
parentId,
siblingIds,
childrenIds,
}: {
rowId: string;
parentId: string;
siblingIds: string[];
childrenIds: string[];
}) => {
const { toggleRow, rowSelectionStatus } = useContext(
NewTableSelectedRowsContext,
) as NewTableSelectedRowsType;
const handleToggle = useCallback(() => {
// todo
}, []);
const status = rowSelectionStatus(rowId);
return <ThreeStateCheckbox checked={status} onChange={handleToggle} />;
};
I have tried to achieve this using a context provider but haven't got any success:
import { createContext, useCallback, useMemo, useState } from "react";
import {
NewTableSelectedRowsType,
} from "./types";
const statusTypes = {
checked: true,
intermediate: null,
unchecked: false,
};
export const NewTableSelectedRowsContext = createContext<NewTableSelectedRowsType | null>(null);
interface Props {
children: JSX.Element;
}
export const NewTableSelectedRowsContextProvider = ({ children }: Props): JSX.Element => {
const rowSelectionStatus = useCallback((rowId: string) => // todo, []);
const toggleRow = useCallback(() => {
// todo
},
[],
);
const value: NewTableSelectedRowsType = useMemo(
() => ({
toggleRow,
rowSelectionStatus,
}),
[rowSelectionStatus, toggleRow],
);
return (
<NewTableSelectedRowsContext.Provider value={value}>
{children}
</NewTableSelectedRowsContext.Provider>
);
};
How can I achieve that in React?
I'm not sure why you are using context to store the logic of selecting rows or not, you will need to send the data.length to compute if the selected rows are the same amount as the table rows/part of absolutely 0.
You also haven't attached to component code, so my answer will be more general...
You can have state for table status and one for selected rows
enum ETableState {
checked = 'checked',
empty = 'empty',
intermediate = 'intermediate'
}
const [tableState, setTableState] = useState<ETableState>(ETableState.intermediate)
const [selectedRows, setSelectedRows] = useState<string[])([])
on each selecting row, you will call a callback for add/remove it from selectedRows state, that after will trigger an effect
const toggleRow = useCallback((id: string) =>
setSelectedRows(prev =>
prev.includes(id) ? prev.filter(i => i != id) : [...prev, id]
)
, [selectedRows.length])
useEffect(() => {
setTableState(dataLength === selectedRows.length ?
ETableState.checked
: !selectedRows.length ?
ETableState.empty
: ETableState.intermediate
}
,[selectedRows.length, dataLength]
edit
In case you want to be able to use this logic in few separate places, as in child rows and so on, you can wrap all of this code in custom hook and call it in each component with the data.length
function useTableState(dataLength: number) {
...
return { selectedRows, tableState, toggleRow }
}
// usage in a table component
const { selectedRows, tableState, toggleRow } = useTableState(data.length)

How can i see my array on my component ? / React / Javascript

I have a element like :
const DropdownElements = [
{
key: 1,
title: "Şehir",
placeholder: "Şehir Seçiniz",
apiUrl: "https://api.npoint.io/995de746afde6410e3bd",
type: "city",
selecteditem: "",
data : [],
},
{
key: 2,
title: "İlçe",
placeholder: "İlçe Seçiniz",
apiUrl: "https://api.npoint.io/fc801dbd3fc23c2c1679", // its my apis. They hold datas from json
type: "district",
selecteditem: "",
data : [],
},
]
I fetching that url in App in useEffect.
const App = () => {
useEffect(() => {
DropdownElements.map((x) => {
fetch(x.apiUrl)
.then((z) => z.json())
.then((vb) => {
x.data=vb // If i write x.data.push(vb) i can see it on my component but its not giving pure array.
console.log(x.data) // I can see my datas perfectly. I trying fill my data.
});
});
}, []);
And i setting it like that :
<Space>
{DropdownElements.map((x) => {
return (
<PickerCompanent
showSearch
selecteditem={idhold}
key={x.key}
placeholder={x.placeholder}
type={x.type}
datasource={x.data} // I gave my datasource x.data that i filled .
onFocus={onFocus}
onChange={z=>onChange(z)}
onFocus={onFocus}
onSearch={onSearch}
></PickerCompanent>
);
})}
</Space>
But in my component when i try write like console.log(props) my datasource is empty array. How can i see my datas on my component ? I need set my array to a state in my component.
It seems like you aren't using any kind of state in your code.
const App = () => {
const [myData, setMyData] = useState();
useEffect(() => {
DropdownElements.map((x) => {
fetch(x.apiUrl)
.then((z) => z.json())
.then((vb) => {
x.data=vb // If i write x.data.push(vb) i can see it on my component but its not giving pure array.
console.log(x.data) // I can see my datas perfectly. I trying fill my data.
// in here you'll want to be adding your data to some state
// e.g.
setMyData(x.data);
});
});
}, []);
Then within your component, use that state:
datasource={myData}
Your object is updating but not view. To achieve this you need have a component state, to which we can update and trigger return again to update view.
const App = () => {
const [myData, setMyData] = useState(DropdownElements);
useEffect(() => {
myData.map((x, i) => {
fetch(x.apiUrl)
.then((z) => z.json())
.then((result) => {
myData[i].data = result;
setMyData(myData);
});
});
}, []);
return (
<Space>
{myData.map((x) => {
return (
<PickerCompanent
showSearch
selecteditem={idhold}
key={x.key}
placeholder={x.placeholder}
type={x.type}
datasource={x.data} // I gave my datasource x.data that i filled .
onFocus={onFocus}
onChange={z=>onChange(z)}
onFocus={onFocus}
onSearch={onSearch}
></PickerCompanent>
);
})}
</Space>
);

Get value of Checkbox in react custom checkbox

I have this dynamic checkbox, that I want to update the state with the selected options only ,I tried to add some checks to filter the state on change , but it seems I am not seeing what went wrong!
const checkBoxesOptions = [
{ id: 1, title: 'serviceOne' },
{ id: 2, title: 'serviceTwo' },
{ id: 3, title: 'serviceThree' },
];
const [selectedCheckBoxes, setSelectedCheckBoxes] = React.useState([]);
{checkBoxesOptions.map((checkBox, i) => (
<CheckBox
key={i}
label={checkBox.title}
value={1}
checked={false}
onChange={value => {
let p = {
title: checkBox.title,
isTrue: value,
};
if (p.isTrue) {
const tempstate = selectedCheckBoxes.filter(
checkbox => checkbox !== checkBox.title
);
console.log('temp state', tempstate);
setSelectedCheckBoxes([...selectedCheckBoxes, p.title]);
}
console.log(p);
}}
/>
))}
The value parameter is the event object.
(event) => {
const value = event.target.checked
<... Rest of the code ...>
}

Ant Design Table component not displaying state-based dataSource change

I am using Ant Design for my React project and I'm having trouble with the Table component. I have a list of tasks to which I add a new task based on a Form content - currently just by adding to an array of objects (taskListMock in the code snippets), the app is not linked to any backend. The form works fine, however, the Table does not refresh, even though the dataSource prop of the Table gets its content directly from the state and the state updates correctly - confirmed by logging and devtools. Curiously, the table refreshes with the new task when I initiate the implemented sorting, so my suspicion is that the Table somehow does not refresh its content from the state change, only on onChange hooks or something, but I'm feeling in a bit of a dead-end - any help would be greatly appreciated since I'm planning to use similar functionality in other Tables.
The structure is pretty simple, I have a TasksIndex.js with the Table as an individual component in TaskListTable.js
TaskListTable.js:
const TaskListTable = (props) => {
const { t } = useTranslation();
const [tableContent, setTableContent] = useState(props.tasks);
return (
<React.Fragment>
<Table
pagination={false}
dataSource={tableContent}
columns={[
{
title: t("tasks.name"),
key: "name",
render: (text) => {
return <p>{text.slug}</p>;
},
},
{
title: t("tasks.dateDue"),
dataIndex: "dateDue",
key: "dateDue",
sorter: (a, b) =>
new Date(a.dateDue).getTime() - new Date(b.dateDue).getTime(),
render: (dateDue) => {
let dateFormatted = moment(dateDue);
return <>{dateFormatted.format("LL")}</>;
},
defaultSortOrder: "ascend",
},
{
title: t("tasks.priority"),
key: "priority",
dataIndex: "priority",
render: (priority) => (
<React.Fragment>
{priority === "low" ? (
<Tag color="geekblue">{t("tasks.lowPriority")}</Tag>
) : (
""
)}
{priority === "normal" ? (
<Tag color="green">{t("tasks.normalPriority")}</Tag>
) : (
""
)}
{priority === "high" ? (
<Tag color="volcano">{t("tasks.highPriority")}</Tag>
) : (
""
)}
</React.Fragment>
),
sorter: (a, b) => {
const priorityOrder = ["low", "normal", "high"];
return (
priorityOrder.indexOf(a.priority) -
priorityOrder.indexOf(b.priority)
);
},
},
{
title: t("tasks.options"),
key: "options",
render: (item) => {
return (
<Checkbox value={item.id}>{t("tasks.setCompleted")}</Checkbox>
);
},
},
]}
></Table>
</React.Fragment>
);
};
export default TaskListTable;
TaskIndex.js:
const TasksIndex = () => {
const [isModalOpen, setModalOpen] = useState(false);
const [taskList, updateTaskList] = useState(taskListMock);
const [form] = Form.useForm();
const addTask = useCallback(
(values) => {
const newTaskList = taskList;
newTaskList.push({
id: taskList[taskList.length - 1] + 1,
slug: values.name,
description: values.description,
dateDue: values.dateDue.format("YYYY-MM-DD"),
priority: values.priority,
checked: false,
});
form.resetFields();
updateTaskList(newTaskList);
closeModal();
},
[taskList, form]
);
const openModal = () => {
setModalOpen(true);
};
const closeModal = () => {
setModalOpen(false);
};
const { t } = useTranslation();
return (
<React.Fragment>
<Title>{t("tasks.tasksOverviewHeader")}</Title>
<Row gutter={[16, 24]}>
<Col className="gutter-row" span={24}>
<TaskListTable tasks={taskList}></TaskListTable>
</Col>
</Row>
...
...
I finally fixed it - it seems that creating a new array and pushing the new task to it was not considered a state change (or perhaps a Table change trigger), unlike using the spread operator. The working code looks like this:
const addTask = (values) => {
const newTask = {
id: taskList[taskList.length - 1] + 1,
slug: values.name,
description: values.description,
dateDue: values.dateDue.format("YYYY-MM-DD"),
priority: values.priority,
checked: false,
};
updateTaskList([...taskList, newTask]);
closeModal();
form.resetFields();
};

Categories

Resources