ts(1128)-Declaration or statement expected at public render() line - reactjs

I have the following code (followed this sample code) which compiles without any errors. The code is making use of the sample code to use jQXWidget.
import React from "react";
import * as ReactDOM from "react-dom";
import JqxTabs from "jqwidgets-scripts/jqwidgets-react-tsx/jqxtabs";
import JqxGrid, {
IGridProps,
jqx
} from "jqwidgets-scripts/jqwidgets-react-tsx/jqxgrid";
import { properties } from ".././properties";
type Props = {
project
};
export interface MyState extends IGridProps {
visible: boolean,
assocProjectElementsVisible: boolean,
project: {},
selectedRowIndex: number,
deleteDialogVisible: boolean
}
export class ProjectElements extends React.PureComponent<Props, MyState> {
private myTabs = React.createRef<JqxTabs>();
private projectSpacesGridElement = React.createRef<HTMLDivElement>();
private pSpacesGridElement = React.createRef<JqxGrid>();
private baseUrl = properties.baseUrlWs;
constructor(props: Props) {
super(props);
/* const deleteRowClick = () => {
const selectedrowindex = this.pSpacesGridElement.current.getselectedrowindex();
if (selectedrowindex > -1) {
this.setState({
selectedRowIndex: selectedrowindex,
deleteDialogVisible: true
})
} */
this.state = {
visible: false,
project: {},
assocProjectElementsVisible: false,
selectedRowIndex: null,
deleteDialogVisible: false,
rendergridrows: (params: any): any[] => {
const data = params.data;
return data;
}
};
}
public render() {
return (
<JqxTabs
ref={this.myTabs}
theme={"arctic"}
width="1390px"
height={560}
initTabContent={this.initWidgets}
>
<ul>
<li style={{ marginLeft: 30 }}>
<div style={{ height: 20, marginTop: 5 }}>
<div style={{ float: "left" }}></div>
<div
style={{
marginLeft: 4,
verticalAlign: "middle",
textAlign: "center",
float: "left"
}}
>
Project Spaces
</div>
</div>
</li>
</ul>
<div style={{ overflow: "hidden" }}>
<div id="jqxGrid" ref={this.projectSpacesGridElement} />
<div style={{ marginTop: 10, height: "15%", width: "100%" }}></div>
</div>
</JqxTabs>
);
}
//Tab 1
private projectSpacesGrid = () => {
const source: any = {
datafields: [
{ name: "id", type: "long" },
{ name: "fileName", type: "string" }
],
datatype: "json",
root: "ProjectElements",
url: this.baseUrl + `api/ProjectElements/search/getProjectElementsByProjectId`
};
const dataAdapter = new jqx.dataAdapter(source, {
//async: false,
autoBind: true,
downloadComplete: (data: any, status: any, xhr: any): void => {
source.totalrecords = parseInt(data["page"].totalElements);
},
formatData: (data: any): any => {
data.value = this.props.project.id;
data.page = data.pagenum;
data.size = data.pagesize;
if (data.sortdatafield && data.sortorder) {
data.sort = data.sortdatafield + "," + data.sortorder;
}
return data;
},
loadError: (xhr: any, status: any, error: any) => {
alert('Error loading "' + source.url + '" : ' + error);
}
});
const columns: IGridProps["columns"] = [
{ datafield: "id", text: "ID Number", width: 100 },
{ datafield: "fileName", text: "Project Name", width: 275 }
];
const grid =
this.state.visible || this.state.assocProjectElementsVisible ? null : (
<div>
<JqxGrid
ref={this.pSpacesGridElement}
width={"100%"}
height={"100%"}
theme={"arctic"}
source={dataAdapter}
columns={columns}
/>
</div>
);
ReactDOM.render(grid, this.projectSpacesGridElement.current!);
};
private initWidgets = (tab: any) => {
switch (tab) {
case 0:
this.projectSpacesGrid();
break;
}
};
}
However,as soon as I uncomment the deleteRowClick method I get an error at public on the public render() line as shown in the screenshot below:
What am I doing wrong after uncommenting the deleteRowClick method?

You're missing a closing } at the end of the deleteRowClick function.

Related

Nextjs: Button onclick toggle in sidebar overlay

I have a case regarding the onclick button to open the "choose template" sidebar.
the interface is like this.
So when the button is pressed, the "choose template" sidebar will appear.
However, when I click the button, the sidebar won't appear. do you think that's why? is there something wrong with my coding? Thank you
My Code=
Editor.jsx
import { useEffect, useRef, useState } from "react";
import { useRouter } from "next/router";
import styles from "./Editor.module.scss";
import PropTypes from "prop-types";
import { Button } from "../../global/Button";
import ColorPicker from "./ColorPicker";
import "react-edit-text/dist/index.css";
import RightArrow from "../../../public/images/qrcode/rightArrow.svg";
let qrCode;
if (typeof window !== "undefined") {
console.log("i am client");
const QRCodeStyling = require("qr-code-styling");
qrCode = new QRCodeStyling({
width: 1000,
height: 1000,
margin: 50,
qrOptions: { typeNumber: "0", mode: "Byte", errorCorrectionLevel: "Q" },
imageOptions: { hideBackgroundDots: true, imageSize: 0.2, margin: 10 },
dotsOptions: {
type: "rounded",
color: "#756ce0",
gradient: {
type: "radial",
rotation: 0,
colorStops: [
{ offset: 0, color: "#aa80f9" },
{ offset: 1, color: "#756ce0" },
],
},
},
backgroundOptions: { color: "#ffffff", gradient: null },
image: "https://i.ibb.co/SrpHzTQ/icon-200px.png",
dotsOptionsHelper: {
colorType: { single: true, gradient: false },
gradient: { linear: true, radial: false, color1: "#6a1a4c", color2: "#6a1a4c", rotation: "0" },
},
cornersSquareOptions: { type: "extra-rounded", color: "#756ce0" },
cornersSquareOptionsHelper: {
colorType: { single: true, gradient: false },
gradient: { linear: true, radial: false, color1: "#000000", color2: "#000000", rotation: "0" },
},
cornersDotOptions: { type: "", color: "#613583", gradient: null },
cornersDotOptionsHelper: {
colorType: { single: true, gradient: false },
gradient: { linear: true, radial: false, color1: "#000000", color2: "#000000", rotation: "0" },
},
backgroundOptionsHelper: {
colorType: { single: true, gradient: false },
gradient: { linear: true, radial: false, color1: "#ffffff", color2: "#ffffff", rotation: "0" },
},
});
}
import { BsFileEarmarkPdfFill } from "react-icons/bs";
import { IoIosArrowBack } from "react-icons/io";
const Editor = ({ isResponsive, template, list, merchant, setActive, color, toggleClick }) => {
const { Template } = template;
const warna = "linear-gradient(180deg, #aa80f9 0%, #756ce0 100%)";
const router = useRouter();
const qrRef = useRef();
const [colorProps, setColorProps] = useState(warna);
useEffect(() => {
setTimeout(() => {
if (qrRef.current) qrCode.append(qrRef.current);
}, 100);
}, [Template]);
useEffect(() => {
qrCode.update({
data: process.env.NEXT_PUBLIC_SHARE_URL + "/" + router.query.shareKey,
});
}, [Template]);
// const handleklik = (e) => {
// console.log('Free pizza!');
// }
return (
<>
<div className={styles.container}>
<ColorPicker colorProps={color} callback={(event) => setColorProps(event)} />
<Button onClick={toggleClick} className={styles.button}>
Open Template
</Button>
<IoIosArrowBack
className={styles.arrow + " " + styles.back}
onClick={() => {
setActive(template.id === 0 ? list.length - 1 : template.id - 1);
}}
/>
<div className={styles.paper_container}>
<Template qrRef={qrRef} merchant={merchant} color={colorProps.hex} isResponsive={isResponsive} />
<div className={styles.paper_overlay} id="paper-overlay">
<BsFileEarmarkPdfFill />
<span>Generating PDF</span>
</div>
</div>
<RightArrow
className={styles.arrow + " " + styles.next}
onClick={() => {
setActive(template.id === list.length - 1 ? 0 : template.id + 1);
}}
/>
</div>
</>
);
};
Editor.propTypes = {
template: PropTypes.string,
list: PropTypes.string,
merchant: PropTypes.string,
setActive: PropTypes.string,
color: PropTypes.string,
isResponsive: PropTypes.bool,
toggleClick: PropTypes.func,
};
export default Editor;
Template.jsx
import { useState, useEffect } from "react";
import Image from "next/image";
import styles from "./Template.module.scss";
import PropTypes from "prop-types";
import { FaCheck } from "react-icons/fa";
import { Button } from "../../global/Button";
const Card = ({ data, active, setActive }) => {
return (
<div className={styles.card} onClick={() => setActive(data.id)}>
<Image src={data.image} alt={"template " + data.id} width={116} height={164} />
{active === data.id && (
<div className={styles.overlay}>
<FaCheck />
</div>
)}
</div>
);
};
const Template = ({ list, active, setActive, download }) => {
const [isResponsive, setResponsive] = useState(typeof window !== "undefined" ? window.innerWidth <= 800 : false);
const [isHide, setHide] = useState(true);
useEffect(() => {
const handleResize = () => {
if (window.innerWidth <= 800) {
setResponsive(true);
} else {
setResponsive(false);
}
};
if (window !== "undefined") window.addEventListener("resize", handleResize);
}, []);
useEffect(() => {
setHide(true);
}, [isResponsive]);
return (
<div className={styles.container}>
<div className={styles.card_container} style={{ right: isHide ? null : 0, position: isResponsive && "fixed" }}>
{isResponsive && !isHide && <div className={styles.template_overlay} onClick={() => setHide(true)} />}
<h3>Pilih Template</h3>
{list.map((template) => {
return <Card data={template} key={template.id} active={active} setActive={setActive} />;
})}
</div>
<Button onClick={download} className={styles.button}>
DOWNLOAD
</Button>
</div>
);
};
Card.propTypes = {
data: PropTypes.string,
active: PropTypes.string,
setActive: PropTypes.string
};
Template.propTypes = {
list: PropTypes.string,
active: PropTypes.string,
setActive: PropTypes.string,
download: PropTypes.string,
isResponsive: PropTypes.bool
};
export default Template;
[sharekey].jsx
import { useState, useEffect } from "react";
import { jsPDF } from "jspdf";
import html2canvas from "html2canvas";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import styles from "./Qrcode.module.scss";
import { useRecoilValue } from "recoil";
import { userProfile } from "../../../utils/recoil";
import Sidebar from "../../../components/global/Wrapper/Sidebar/index";
import Editor from "../../../components/edit-survey/qrcode/Editor";
import Template from "../../../components/edit-survey/qrcode/Template";
import TemplateOne from "../../../components/edit-survey/qrcode/template/One";
import TemplateTwo from "../../../components/edit-survey/qrcode/template/Two";
import TemplateThree from "../../../components/edit-survey/qrcode/template/Three";
import TemplateFour from "../../../components/edit-survey/qrcode/template/Four";
import TemplateFive from "../../../components/edit-survey/qrcode/template/Five";
import TemplateSix from "../../../components/edit-survey/qrcode/template/Six";
const Template1 = "/images/qrcode/Template1.jpg";
const Template2 = "/images/qrcode/Template2.jpg";
const Template3 = "/images/qrcode/Template3.jpg";
const Template4 = "/images/qrcode/Template4.jpg";
const Template5 = "/images/qrcode/Template5.jpg";
const Template6 = "/images/qrcode/Template6.jpg";
const templateList = [
{ Template: TemplateOne, id: 0, image: Template1 },
{ Template: TemplateTwo, id: 1, image: Template2 },
{ Template: TemplateThree, id: 2, image: Template3 },
{ Template: TemplateFour, id: 3, image: Template4 },
{ Template: TemplateFive, id: 4, image: Template5 },
{ Template: TemplateSix, id: 5, image: Template6 },
];
const color = {
r: "170",
g: "128",
b: "249",
a: "1",
};
const Qrcode = () => {
const [activeTemplate, setActiveTemplate] = useState(0);
const profile = useRecoilValue(userProfile);
const [isResponsive, setResponsive] = useState(typeof window !== "undefined" ? window.innerWidth <= 800 : false);
const [isHide, setHide] = useState(true);
useEffect(() => {
const handleResize = () => {
if (window.innerWidth <= 800) {
setResponsive(true);
} else {
setResponsive(false);
}
};
if (window !== "undefined") window.addEventListener("resize", handleResize);
}, []);
useEffect(() => {
setHide(true);
}, [isResponsive]);
const download = () => {
const paper = document.getElementById("paper-pdf");
const overlay = document.getElementById("paper-overlay");
overlay.style.display = "flex";
paper.style.transform = "scale(2)";
html2canvas(paper).then((canvas) => {
const imgData = canvas.toDataURL("image/jpg");
const pdf = new jsPDF({
orientation: "potrait",
unit: "pt",
format: "a5",
});
const imgProps = pdf.getImageProperties(imgData);
const pdfWidth = pdf.internal.pageSize.getWidth();
const pdfHeight = (imgProps.height * pdfWidth) / imgProps.width;
pdf.addImage(imgData, "JPG", 0, 0, pdfWidth, pdfHeight);
pdf.save("QRCode.pdf");
paper.style.transform = null;
overlay.style.display = null;
});
};
return (
<Sidebar style={{ paddingTop: 0, paddingBottom: 0, paddingRight: 0 }}>
<div className={styles.container}>
<Editor
color={color}
template={templateList[activeTemplate]}
list={templateList}
merchant={profile.merchant}
setActive={setActiveTemplate}
toggleClick={() => setHide(!isHide)}
isResponsive={isResponsive}
/>
<Template list={templateList} active={activeTemplate} setActive={setActiveTemplate} download={download} isResponsive={isResponsive} />
</div>
</Sidebar>
);
};
export const getServerSideProps = async ({ locale }) => ({
props: {
...(await serverSideTranslations(locale, ["common", "edit-survey"])),
},
});
export default Qrcode;
you should pass down the isHide and setIsHide down to Template component. and remove the isHide, setIsHide useState from the Template component
so in [sharekey].jsx
<Template list={templateList} active={activeTemplate} setActive={setActiveTemplate} download={download} isResponsive={isResponsive} isHide={isHide} setIsHide={setIsHide}/>

How to make all process entries in History button using typescript

Is that possible to fetch data or using function useEffect..I have tried all the way. any suggestion?**************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************
This is my processes.tsx file.
import React, { useEffect, useState } from 'react';
import { connect, ConnectedProps } from 'react-redux';
import { Link, RouteComponentProps, useHistory, withRouter } from 'react-router-dom';
import { People } from '#microsoft/mgt-react';
import { checkPermission } from '../../../config/permission-utils';
import { RootState } from '../../../config/store';
import { getDateFormatted, getQueryParams } from '../../../config/utils';
import { Modal } from '../../../components';
import __ from '../../../lang';
import {
addProcessAction,
clearSelectProcessAction,
deleteProcessAction,
makeProcessVersionAction,
searchProcessesAction,
selectProcessAction,
selectProcessByIdAction,
setMetaAction,
shareProcessAction,
updateProcessAction,
} from '../../../store/actions/process-actions';
import { addProcessInstanceAction, fetchModelingInstances } from '../../../store/actions/process-instance-actions';
import { Role, Department, Process } from '../../../ts/interfaces';
import { ProcessFilter } from '../process-filter';
import { ProcessForm } from './process-form';
import { SendForm } from './send-form';
import {
DetailsList,
DetailsListLayoutMode,
Selection,
SelectionMode,
IColumn,
IconButton,
PrimaryButton,
Text,
Stack,
TooltipHost,
DirectionalHint,
ActionButton,
ButtonType,
IDetailsRowStyles,
IDetailsListProps,
DetailsRow,
Spinner,
SpinnerSize,
} from '#fluentui/react';
import { Popover } from '../../../components';
import { getStyles, gridStyles } from './processes-styles';
import { ProcessActionsMenu } from './process-actions-menu';
import { useWindowSize } from '../../../hooks';
import { ProcessVersionsHistory } from '../../process-details/history/versions';
const Processes = (props: RouteComponentProps & PropsFromRedux) => {
const {
ProcessReducer: { processes, selected_process },
ProcessGroupReducer: { processgroups },
DepartmentReducer: { departments },
RoleReducer: { roles },
UserReducer,
location: { search },
selectProcessByIdAction,
clearSelectProcessAction,
selectProcessAction,
addProcessAction,
updateProcessAction,
shareProcessAction,
fetchModelingInstances,
addProcessInstanceAction,
makeProcessVersionAction,
deleteProcessAction,
searchProcessesAction,
} = props;
const [filterVisible, setFilterVisible] = useState(false);
const [modalVisible, setModalVisible] = useState(false);
const [sendVisible, setSendVisible] = useState(false);
const [enableDelete, setEnableDelete] = useState<boolean>(false);
const [selectedProcesses, setSelectedProcesses] = useState<Process[]>([]);
const [showSpinner, setShowSpinner] = useState<boolean>(false);
const [width] = useWindowSize({ checkDocumentSize: true });
const processesStyles = getStyles(width);
const [showHistoryModal, setShowHistoryModal] = useState<boolean>(false);
const history = useHistory();
const [selectedProcessId, setSelectedProcessId] = useState<number>();
const _rowsSelected: Selection = new Selection({
onSelectionChanged: () => _getSelectionDetails(),
});
const _getSelectionDetails = () => {
const selectionCount = _rowsSelected.getSelectedCount();
if (selectionCount > 0) {
setSelectedProcesses(_rowsSelected.getSelection() as Process[]);
setEnableDelete(true);
} else {
setSelectedProcesses([]);
setEnableDelete(false);
}
};
const columns: IColumn[] = [
{
key: 'process_name',
name: __('process'),
fieldName: 'process_name',
minWidth: 90,
maxWidth: 90,
isResizable: true,
onRender: (record: Process) =>
record && (
<Link className={processesStyles.cellText} to={`/process-management/processes/${record.id}`}>
{record.process_name}
</Link>
),
},
{
name: __('version'),
fieldName: 'version_number',
key: 'version_number',
minWidth: 30,
maxWidth: 60,
isResizable: true,
onRender: ({ version_number }: Process) => <div className={processesStyles.cellText}>{version_number}</div>,
},
{
name: __('process group'),
fieldName: 'group',
key: 'group',
minWidth: 90,
maxWidth: 120,
isResizable: true,
className: processesStyles.cellText,
onRender: (record: Process) =>
record.group && record.group.group_name.trim() !== '' ? (
<div className={processesStyles.cellText}>{record.group.group_name}</div>
) : (
<>
{
<div className="warning-icon blink">
<IconButton iconProps={{ iconName: 'Warning' }} onClick={() => onProcessEdit(record)} />
</div>
}
</>
),
},
{
name: __('department'),
fieldName: 'department',
key: 'department',
minWidth: 90,
maxWidth: 120,
isResizable: true,
onRender: (record: Process) =>
record.department.length > 0 ? (
record.department.map((item: Department, i: number) => (
<div className={processesStyles.cellText}>
{item.department_name}
{i < record.department.length - 1 ? ', ' : ''}
</div>
))
) : (
<div className="warning-icon blink">
<IconButton iconProps={{ iconName: 'Warning' }} onClick={() => onProcessEdit(record)} />
</div>
),
},
{
name: __('created at'),
fieldName: 'date_created',
key: 'date_created',
minWidth: 90,
maxWidth: 120,
isResizable: true,
onRender: ({ date_created }: Process) => (
<div className={processesStyles.cellText}>{date_created && getDateFormatted(date_created, 'lll')}</div>
),
},
{
name: __('location'),
fieldName: 'location',
key: 'location',
minWidth: 90,
maxWidth: 120,
isResizable: true,
onRender: ({ location }: Process) => <div className={processesStyles.cellText}>{location}</div>,
},
{
name: __('process owner'),
fieldName: 'process_owner',
key: 'process_owner',
minWidth: 90,
maxWidth: 120,
isResizable: true,
onRender: ({ process_owner, id }: Process) =>
process_owner && process_owner.length ? renderPersonsWithPopover(process_owner, id, 'process_owner') : '',
},
{
name: __('created by'),
fieldName: 'created_by',
key: 'created_by',
minWidth: 90,
maxWidth: 120,
isResizable: true,
onRender: ({ created_by }: Process) => created_by && <People userIds={created_by.username.split(',')}></People>,
},
{
name: __('sent to recording'),
fieldName: 'send_to',
key: 'send_to',
minWidth: 90,
maxWidth: 180,
onRender: ({ send_to, id }: Process) =>
send_to && send_to.length ? renderPersonsWithPopover(send_to, id, 'send_to') : '',
},
{
name: __('status'),
fieldName: 'status',
key: 'status',
minWidth: 200,
maxWidth: 200,
onRender: (record: Process) => {
return (
<>
<Stack wrap horizontal>
<Stack.Item>
<div className={processesStyles.tagStyles} style={{ background: record.status.color }}>
<Text variant={'smallPlus'}>{__(record.status.project_status_name)}</Text>
</div>
</Stack.Item>
<Stack.Item>
{record.status &&
checkPermission(UserReducer.permissions, 'change_processinstance') &&
record.status.project_status_name === 'in-process recording' && (
<TooltipHost content={__('view the process review')} directionalHint={DirectionalHint.bottomCenter}>
<Link to={`/process-management/processes/${record.id}/applications`}>
<IconButton iconProps={{ iconName: 'SingleColumnEdit' }} />
</Link>
</TooltipHost>
)}
{record.status &&
checkPermission(UserReducer.permissions, 'add_process') &&
(record.status.project_status_name === 'done' || record.status.project_status_name === 'rejected') &&
!hasCopiedProcess(record) && (
<Popover
content={__('are you sure to copy this process?')}
target={`process-${record.id}`}
enableConfirm={true}
onOk={() => onMakeVersion(record)}
>
<TooltipHost content={__('make new version')} directionalHint={DirectionalHint.bottomCenter}>
<IconButton id={`process-${record.id}`} iconProps={{ iconName: 'Copy' }} />
</TooltipHost>
</Popover>
)}
</Stack.Item>
</Stack>
</>
);
},
},
{
name: '',
key: 'actions',
minWidth: 30,
maxWidth: 30,
onRender: (record: Process) => {
const actions = [];
if (record.status && checkPermission(UserReducer.permissions, 'change_process')) {
{
(record.status.project_status_name === 'in-process recording' ||
record.status.project_status_name === 'new') &&
actions.push(
<TooltipHost content={__('edit process')} directionalHint={DirectionalHint.bottomCenter}>
<IconButton onClick={() => onProcessEdit(record)} iconProps={{ iconName: 'Edit' }} />
</TooltipHost>,
);
}
}
return actions;
},
},
];
useEffect(() => {
clearSelectProcessAction();
}, []);
here we can make something different..
useEffect(() => {
getProcesses();
}, [search]);
const getProcesses = () => {
const query =
search === '' ? '?order_by=-date_created&outdated=false' : `${search}&order_by=-date_created&outdated=false`;
searchProcessesAction(query);
};
/**
* checks if
* #param record a process in the process list
* #returns
*/
const hasCopiedProcess = (record: Process): boolean => {
const unDoneCopy = processes.find((process) => {
if (process && !process.root_version) {
return false;
}
return (
(process?.root_version?.id === record.id || process?.root_version?.id === record?.root_version?.id) &&
process.status.project_status_name !== 'done'
);
});
return unDoneCopy ? true : false;
};
const showProcessModal = () => {
selectProcessByIdAction(0);
setModalVisible(true);
};
const onProcessSend = (item: Process): void => {
selectProcessByIdAction(item.id);
setSendVisible(true);
};
const mapRaciFields = (process: any) => {
let { accountable, consulted, informed, responsible } = process;
// here a RACIField is overwritten bei {departments:string[]; roles:string[]; employees[string]}. Should be improved to be more readable
const fields = [accountable, consulted, informed, responsible];
[accountable, consulted, informed, responsible] = fields.map((field) => {
if (field) {
return {
departments: field.departments && field.departments.map((i: Department) => i.resource_uri),
roles: field.roles && field.roles.map((i: Role) => i.resource_uri),
employees: field.employees,
};
} else {
return null;
}
});
return [accountable, consulted, informed, responsible];
};
const startModeling = (process: Process) => {
fetchModelingInstances(process.id, (res: any) => {
if (res.data.objects.length) {
const redirect = `/process-management/processes/${res.data.objects[0].id}/modeling-tool/`;
history.push(redirect);
} else {
const { resource_uri, description, process_name } = process;
const [accountable, consulted, informed, responsible] = mapRaciFields(process);
const data = [
{
responsible: responsible ? { ...responsible, id: null } : null,
accountable: accountable ? { ...accountable, id: null } : null,
informed: informed ? { ...informed, id: null } : null,
consulted: consulted ? { ...consulted, id: null } : null,
source_process: resource_uri,
description: description ? description : null,
instance_name: process_name,
is_in_modeling: true,
is_sent: true,
},
];
addProcessInstanceAction({ objects: data }, (res: any) => {
const redirect = `/process-management/processes/${res.data.objects[0].id}/modeling-tool/`;
history.push(redirect);
});
}
});
};
//------------------
const onMakeVersion = (record: Process) => {
if (record.approved_by) {
delete record.approved_by;
}
setShowSpinner(true);
makeProcessVersionAction(record, (res) => {
setShowSpinner(false);
getProcesses();
});
};
const onProcessEdit = (item: Process) => {
selectProcessByIdAction(item.id);
setModalVisible(true);
};
const onDeleteProcesses = () => {
for (const process of selectedProcesses) {
deleteProcessAction(process);
}
getProcesses();
setEnableDelete(false);
};
const renderPersonsWithPopover = (persons: string, id: number, columnName: string) => {
const peopleArray = (persons && persons.split(',')) || [];
const popoverContent = peopleArray?.length > 3 ? getPopoverContent(peopleArray) : null;
return [
popoverContent ? (
<Popover content={popoverContent} target={`${columnName}-${id}`}>
<People id={`${columnName}-${id}`} userIds={peopleArray} showMax={3}></People>
</Popover>
) : (
<People userIds={peopleArray} showMax={3}></People>
),
];
};
const getPopoverContent = (peopleArray: string[]) => {
return <People userIds={peopleArray.slice(3)} showMax={peopleArray.length - 3}></People>;
};
const showProcessInstances = (id: number) => {
setSelectedProcessId(id);
};
const renderProcessesBtns = () => {
return (
<div className={processesStyles.btnsHolderContainer}>
<div className={processesStyles.btnsHolder}>
<Stack horizontal>
{selectedProcesses.length === 1 && (
<div className={processesStyles.processActions}>
<ProcessActionsMenu
UserReducer={UserReducer}
process={selectedProcesses[0]}
sendProcess={() => onProcessSend(selectedProcesses[0])}
// startModelling={() => onStartModeling(selectedProcesses[0])}
startModeling={() => startModeling(selectedProcesses[0])}
/>
</div>
)}
{enableDelete && checkPermission(UserReducer.permissions, 'delete_process') && (
<Popover
title={__('delete selected processes')}
content={__('are you sure to delete this processes?')}
target={'delete-processes'}
enableConfirm={true}
onOk={() => onDeleteProcesses()}
>
<ActionButton
id="delete-processes"
iconProps={{ iconName: 'Delete' }}
style={{ margin: '5px', float: 'right' }}
buttonType={ButtonType.default}
>
{__('delete')}
</ActionButton>
</Popover>
)}
{
<TooltipHost content={__('filter processes')} directionalHint={DirectionalHint.bottomCenter}>
<ActionButton
iconProps={{ iconName: 'Filter' }}
style={{ margin: '5px', float: 'right' }}
onClick={() => setFilterVisible(true)}
/>
</TooltipHost>
}
{
<TooltipHost content={__('History')} directionalHint={DirectionalHint.bottomCenter}>
<ActionButton
iconProps={{ iconName: 'History' }}
style={{ margin: '5px', float: 'right' }}
onClick={() => setShowHistoryModal(true)}
/>
{showHistoryModal && (
<Modal
title={__('history')}
isModalOpen={showHistoryModal}
hideFooter={true}
onCancel={() => {
setShowHistoryModal(false);
}}
>
<ProcessVersionsHistory selected_process={selected_process} showProcessInstances={showProcessInstances} />
</Modal>
)}
</TooltipHost>
}
{checkPermission(UserReducer.permissions, 'add_process') && (
<PrimaryButton
onClick={() => showProcessModal()}
style={{ margin: '5px', float: 'right' }}
iconProps={{ iconName: 'Add' }}
>
{__('new')}
</PrimaryButton>
)}
</Stack>
</div>
<div className={processesStyles.clear}></div>
</div>
);
};
const _onRenderRow: IDetailsListProps['onRenderRow'] = (props) => {
const customStyles: Partial<IDetailsRowStyles> = {};
if (props) {
customStyles.root = {
zIndex: 0,
transition: 'z-index 2s',
'&:hover': {
zIndex: 1000,
},
};
return <DetailsRow {...props} styles={customStyles} />;
}
return null;
};
return (
<>
{renderProcessesBtns()}
<div className={processesStyles.tableWrapper}>
<Stack className={processesStyles.table}>
<DetailsList
items={processes}
columns={columns}
selectionMode={SelectionMode.multiple}
setKey="none"
layoutMode={DetailsListLayoutMode.justified}
isHeaderVisible={true}
selection={_rowsSelected}
onRenderRow={_onRenderRow}
styles={gridStyles}
/>
</Stack>
</div>
{filterVisible && <ProcessFilter filterVisible={filterVisible} setFilterVisible={setFilterVisible} />}
{modalVisible && (
<ProcessForm
visible={modalVisible}
setVisible={setModalVisible}
add={addProcessAction}
update={updateProcessAction}
select={selectProcessAction}
selected={selected_process}
departments={departments}
roles={roles}
groups={processgroups}
UserReducer={UserReducer}
/>
)}
{sendVisible && selected_process && (
<SendForm
visible={sendVisible}
setVisible={setSendVisible}
select={selectProcessAction}
selected={selected_process}
departments={departments}
roles={roles}
send={shareProcessAction}
afterSend={() => getProcesses()}
mapRaciFields={mapRaciFields}
/>
)}
{showSpinner && (
<div className={processesStyles.spinnerOverlay}>
<Spinner size={SpinnerSize.large} className={processesStyles.spinner} />
</div>
)}
</>
);
};
type PropsFromRedux = ConnectedProps<typeof connector>;
const mapStateToProps = ({
UserReducer,
ProcessReducer,
ProcessGroupReducer,
StatusReducer,
DepartmentReducer,
RoleReducer,
}: RootState) => ({
UserReducer,
ProcessReducer,
ProcessGroupReducer,
StatusReducer,
DepartmentReducer,
RoleReducer,
});
const connector = connect(mapStateToProps, {
setMetaAction,
selectProcessByIdAction,
clearSelectProcessAction,
selectProcessAction,
addProcessAction,
updateProcessAction,
shareProcessAction,
fetchModelingInstances,
addProcessInstanceAction,
makeProcessVersionAction,
deleteProcessAction,
searchProcessesAction,
});
export default connector(withRouter(Processes));
for start and visible model is working but data can not be displayed.

fluent ui details List implementation in Functional component

Can anybody send code on how to implement fluent UI details List in Functional Component(https://developer.microsoft.com/en-us/fluentui#/controls/web/detailslist/basic) and how to fetch data from API to details List
That's a start you will need to "refact" this code by the way this is a really good practice :
import * as React from "react";
import { Announced } from "office-ui-fabric-react/lib/Announced";
import {
TextField,
ITextFieldStyles
} from "office-ui-fabric-react/lib/TextField";
import {
DetailsList,
DetailsListLayoutMode,
Selection,
IColumn
} from "office-ui-fabric-react/lib/DetailsList";
import { MarqueeSelection } from "office-ui-fabric-react/lib/MarqueeSelection";
import { Fabric } from "office-ui-fabric-react/lib/Fabric";
import { mergeStyles } from "office-ui-fabric-react/lib/Styling";
import { Text } from "office-ui-fabric-react/lib/Text";
const exampleChildClass = mergeStyles({
display: "block",
marginBottom: "10px"
});
const textFieldStyles: Partial<ITextFieldStyles> = {
root: { maxWidth: "300px" }
};
export interface IDetailsListBasicExampleItem {
key: number;
name: string;
value: number;
}
export interface IDetailsListBasicExampleState {
items: IDetailsListBasicExampleItem[];
selectionDetails: string;
}
export const DetailsListBasicExampleFunction: React.FunctionComponent<
{} | IDetailsListBasicExampleState
> = () => {
const _allItems: IDetailsListBasicExampleItem[] = [];
const [selection, setSelection] = React.useState<Selection | undefined>();
function _getSelectionDetails(): string {
const selectionCount = selection ? selection.getSelectedCount() : 0;
switch (selectionCount) {
case 0:
return "No items selected";
case 1:
return (
"1 item selected: " +
(selection.getSelection()[0] as IDetailsListBasicExampleItem).name
);
default:
return `${selectionCount} items selected`;
}
}
const [state, setState] = React.useState({
items: _allItems,
selectionDetails: _getSelectionDetails()
});
React.useEffect(() => {
const _selection: Selection = new Selection({
onSelectionChanged: () =>
setState((prev) => {
return { ...prev, selectionDetails: _getSelectionDetails() };
})
});
setSelection(_selection);
for (let i = 0; i < 200; i++) {
_allItems.push({
key: i,
name: "Item " + i,
value: i
});
}
setState((prev) => {
return { ...prev, items: _allItems };
});
}, []);
const _columns: IColumn[] = [
{
key: "column1",
name: "Name",
fieldName: "name",
minWidth: 100,
maxWidth: 200,
isResizable: true
},
{
key: "column2",
name: "Value",
fieldName: "value",
minWidth: 100,
maxWidth: 200,
isResizable: true
}
];
// Populate with items for demos.
const _onFilter = (
ev: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
text: string
): void => {
console.log(text);
setState((prev) => {
return {
...prev,
items: text
? _allItems.filter((i) => i.name.toLowerCase().indexOf(text) > -1)
: _allItems
};
});
};
const _onItemInvoked = (item: IDetailsListBasicExampleItem): void => {
alert(`Item invoked: ${item.name}`);
};
return selection ? (
<Fabric>
<div className={exampleChildClass}>{state.selectionDetails}</div>
<Text>
Note: While focusing a row, pressing enter or double clicking will
execute onItemInvoked, which in this example will show an alert.
</Text>
<Announced message={state.selectionDetails} />
<TextField
className={exampleChildClass}
label="Filter by name:"
onChange={(e, t) => _onFilter(e, t ?? "")}
styles={textFieldStyles}
/>
<Announced
message={`Number of items after filter applied: ${state.items.length}.`}
/>
<MarqueeSelection selection={selection}>
<DetailsList
items={state.items}
columns={_columns}
setKey="set"
layoutMode={DetailsListLayoutMode.justified}
selection={selection}
selectionPreservedOnEmptyClick={true}
ariaLabelForSelectionColumn="Toggle selection"
ariaLabelForSelectAllCheckbox="Toggle selection for all items"
checkButtonAriaLabel="select row"
onItemInvoked={_onItemInvoked}
/>
</MarqueeSelection>
</Fabric>
) : (
<div>Loading</div>
);
};
UPDATE
To pass this sample of code in JSX this is pretty easy you just need to remove all type thing.
And to fetch data I use axios.
see the code below:
import * as React from "react";
import { Announced } from "office-ui-fabric-react/lib/Announced";
import { TextField } from "office-ui-fabric-react/lib/TextField";
import {
DetailsList,
DetailsListLayoutMode,
Selection
} from "office-ui-fabric-react/lib/DetailsList";
import { MarqueeSelection } from "office-ui-fabric-react/lib/MarqueeSelection";
import { Fabric } from "office-ui-fabric-react/lib/Fabric";
import { mergeStyles } from "office-ui-fabric-react/lib/Styling";
import { Text } from "office-ui-fabric-react/lib/Text";
import axios from "axios";
const exampleChildClass = mergeStyles({
display: "block",
marginBottom: "10px"
});
const textFieldStyles = {
root: { maxWidth: "300px" }
};
export const DetailsListBasicExampleFunction = () => {
const _allItems = [];
const [selection, setSelection] = React.useState();
function _getSelectionDetails() {
const selectionCount = selection ? selection.getSelectedCount() : 0;
switch (selectionCount) {
case 0:
return "No items selected";
case 1:
return "1 item selected: " + selection.getSelection()[0].name;
default:
return `${selectionCount} items selected`;
}
}
const [state, setState] = React.useState({
items: _allItems,
selectionDetails: _getSelectionDetails()
});
React.useEffect(() => {
const _selection = new Selection({
onSelectionChanged: () =>
setState((prev) => {
return { ...prev, selectionDetails: _getSelectionDetails() };
})
});
setSelection(_selection);
//********************** */fetch data from api***************************************
axios
.get("/data.json") //pass your url in param
.then((res) =>
setState((prev) => {
return { ...prev, items: res.data };
})
); //pass data in setState
}, []);
const _columns = [
{
key: "column1",
name: "Name",
fieldName: "name",
minWidth: 100,
maxWidth: 200,
isResizable: true
},
{
key: "column2",
name: "Value",
fieldName: "value",
minWidth: 100,
maxWidth: 200,
isResizable: true
}
];
// Populate with items for demos.
const _onFilter = (ev, text) => {
console.log(text);
setState((prev) => {
return {
...prev,
items: text
? _allItems.filter((i) => i.name.toLowerCase().indexOf(text) > -1)
: _allItems
};
});
};
const _onItemInvoked = (item) => {
alert(`Item invoked: ${item.name}`);
};
return selection ? (
<Fabric>
<div className={exampleChildClass}>{state.selectionDetails}</div>
<Text>
Note: While focusing a row, pressing enter or double clicking will
execute onItemInvoked, which in this example will show an alert.
</Text>
<Announced message={state.selectionDetails} />
<TextField
className={exampleChildClass}
label="Filter by name:"
onChange={(e, t) => _onFilter(e, t ?? "")}
styles={textFieldStyles}
/>
<Announced
message={`Number of items after filter applied: ${state.items.length}.`}
/>
<MarqueeSelection selection={selection}>
<DetailsList
items={state.items}
columns={_columns}
setKey="set"
layoutMode={DetailsListLayoutMode.justified}
selection={selection}
selectionPreservedOnEmptyClick={true}
ariaLabelForSelectionColumn="Toggle selection"
ariaLabelForSelectAllCheckbox="Toggle selection for all items"
checkButtonAriaLabel="select row"
onItemInvoked={_onItemInvoked}
/>
</MarqueeSelection>
</Fabric>
) : (
<div>Loading</div>
);
};

one react component updates all the other react components

I have a react table and one of the columns of it is another component. This component is a dropdown which get its value with an API call which I have defined in componentDidMount().
I have use case where in if user selects any value from the dropdown, I want to save that field to the DB. So I defined this post call in the handleChange function of the dropdown.
Issue is that when I change the value in any one row, every other component in other rows also calls the makes the network calls which is defined in componentDidMount(). So componentDidMount() is called for all the 4 entries. I confirmed on the server side as well. I can see four get requests(I have only 4 rows for now). I am thoroughly confused why it's behaving this way?
Parent Component
import React from 'react';
import ReactTable from 'react-table';
import 'react-table/react-table.css';
import Popup from "reactjs-popup";
export default class DetailsTable extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
shipmentDataMap : { },
selectedRow: null,
downloadableAlerts: []
};
this.setState = this.setState.bind(this);
this.handleRowClick = this.handleRowClick.bind(this);
this.handleReassignment = this.handleReassignment.bind(this);
this.handleStatusUpdate = this.handleStatusUpdate.bind(this);
this.generateFilteredArr = this.generateFilteredArr.bind(this);
this.handleDownload = this.handleDownload.bind(this);
this.updateActualEntity = this.updateActualEntity.bind(this);
};
componentDidMount() {
axios.post('/entity/getRoute', {
trackingId: this.state.tid
})
.then((response) => {
let tempRoute = [];
response.data.route.forEach(element => {
tempRoute.push({ label: element['node'], value: element['node'] });
})
this.setState({route: tempRoute});
})
.catch(function (error) {
console.log(error);
});
};
updateActualEntity = (trackingId, updatedEntity) => {
let updatedRecord = this.state.shipmentDataMap[trackingId];
updatedRecord.actualEntity = updatedEntity;
this.setState({shipmentDataMap: this.state.shipmentDataMap});
};
render() {
const TableColumns = [{
Header: 'Actions',
id: 'actionPopupButton',
filterable: false,
style: {'textAlign': 'left'},
Cell: row => (<div><ReassignPopup data={row.original} updateRowFunc={this.handleReassignment} nodeOptions={this.props.nodeOptions}/>
<br/>
<UpdateStatusPopup data={row.original} updateRowFunc={this.handleStatusUpdate} statusOptions={this.props.statusOptions}/>
</div>)
},
{
Header: 'Assigned Node',
headerStyle: {'whiteSpace': 'unset'},
accessor: 'node',
style: {'whiteSpace': 'unset'}
}, {
Header: 'TID',
headerStyle: {'whiteSpace': 'unset'},
accessor: 'tid',
width: 140,
filterMethod: (filter, row) => {
return row[filter.id].startsWith(filter.value)
},
Cell: props => {props.value}
},
{
Header: 'Predicted Entity',
headerStyle: {'whiteSpace': 'unset'},
filterable: false,
accessor: 'predictedEntity',
style: {'whiteSpace': 'unset'},
},
{
Header: 'Feedback',
headerStyle: {'whiteSpace': 'unset'},
filterable: false,
accessor: 'actualEntity',
width: 140,
style: {'whiteSpace': 'unset', overflow: 'visible'},
Cell: row => (<div><AbusiveEntityComponent entity={row.original.actualEntity}
tid={row.original.tid} trackingDetailsId={row.original.trackingDetailsId}
updateActualEntityInShipmentData={this.updateActualEntity}/></div>)
}
return <div>
<CSVLink data={this.state.downloadableAlerts} filename="ShipmentAlerts.csv" className="hidden" ref={(r) => this.csvLink = r} target="_blank"/>
<ReactTable
ref={(r)=>this.reactTable=r}
className='-striped -highlight'
filterable
data={Object.values(this.state.shipmentDataMap)}
//resolveData={data => data.map(row => row)}
columns={TableColumns}
//filtered={this.state.filtered}
filtered={this.generateFilteredArr(this.props.filterMap, this.props.searchParams)}
/*onFilteredChange={(filtered, column, value) => {
this.onFilteredChangeCustom(value, column.id || column.accessor);
}}*/
defaultFilterMethod={(filter, row, column) => {
const id = filter.pivotId || filter.id;
if (typeof filter.value === "object") {
return row[id] !== undefined
? filter.value.indexOf(row[id].toString()) > -1
: true;
} else {
return row[id] !== undefined
? String(row[id]).indexOf(filter.value) > -1
: true;
}
}}
defaultPageSize={10}
//pageSize={10}
previousText='Previous Page'
nextText='Next Page'
noDataText='No intervention alerts found'
style={{
fontSize: "12px",
height: "67.4vh" // Using fixed pixels/limited height will force the table body to overflow and scroll
}}
getTheadFilterProps={() => {return {style: {display: "none" }}}}
getTbodyProps={() => {return {style: {overflowX: "hidden" }}}} //For preventing extra scrollbar in Firefox/Safari
/*
getTrProps={(state, rowInfo) => {
if (rowInfo && rowInfo.row) {
return {
onClick: (e) => {this.handleRowClick(e, rowInfo)},
style: {
//background: rowInfo.index === this.state.selectedRow ? '#00afec' : 'white',
color: rowInfo.index === this.state.selectedRow ? 'blue' : 'black'
}
}
} else {
return {}
}
}
} */
/>
</div>;
}
}
Child Component
import React from 'react';
import axios from 'axios';
export default class AbusiveEntityComponent extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
entity: this.props.entity,
tid: this.props.tid,
trackingDetailsId: this.props.trackingDetailsId,
route: []
};
this.handleChange = this.handleChange.bind(this);
}
handleChange = (event) => {
var selected = event.target.value;
if(selected !== '' && this.state.entity !== selected) {
if (window.confirm('Are you sure you want to select: '+ selected)) {
axios.post('/entity/upateAbusiveEntity', {
trackingDetailsId: this.state.trackingDetailsId,
abusiveEntity: selected
}).then( (response) =>{
this.setState({entity: selected});
this.props.updateActualEntityInShipmentData(this.state.tid, selected);
})
.catch(function (error) {
console.log(error);
});
}
}
}
componentDidMount() {
console.log("did mount");
axios.get('/entity/getRoute', {
params: {
trackingId: this.state.tid
}
})
.then((response) => {
let tempRoute = [];
let prev="";
response.data.route.forEach(element => {
if(prev!== "") {
tempRoute.push(prev+"-"+element['node'])
}
tempRoute.push(element['node']);
prev=element['node'];
})
this.setState({route: [''].concat(tempRoute)});
})
.catch(function (error) {
console.log(error);
});
};
render() {
return (
<div className="AbusiveEntityDiv">
<select onChange={this.handleChange} value={this.state.entity===null?'':this.state.entity}
style={{width: 100}}>
{ this.state.route.map(value => <option key={value} value={value}>{value}</option>) }
</select>
</div>
);
}
}
My question is if componentDidUpdate() is not the correct place to fetch data for dropdown, where should I define the network call ?
I found the solution. In the parent component I maintain a state of shipmentstatusmap. One of the columns of this map is acutalEntity. Now in the child component, whenever user selects the value from dropdown, I callback the parent to update the shipmentStatusMap as well. This callback was my problem.
Because now the state of parent component changes, it unmount the child and re-mount it. So it's componentDidMount is called for all the rows which in turn makes the API call.
Solution
Since I want the dropdown values only once when whole parent component is loaded, I can either move the API to constructor or the in the componentDidMount() of parent. Fetching data in constructor is not a good idea .
So I moved this API call in parent and voila! everything works as expected.
updated code:
Child component
import React from 'react';
import axios from 'axios';
export default class AbusiveEntityComponent extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
entity: this.props.entity,
tid: this.props.tid,
trackingDetailsId: this.props.trackingDetailsId,
route: this.props.route
};
this.handleChange = this.handleChange.bind(this);
}
handleChange = (event) => {
var selected = event.target.value;
if(selected !== '' && this.state.entity !== selected) {
if (window.confirm('Are you sure you want to select: '+ selected)) {
axios.post('/entity/upateAbusiveEntity', {
trackingDetailsId: this.state.trackingDetailsId,
abusiveEntity: selected
}).then( (response) =>{
this.setState({entity: selected});
this.props.updateActualEntityInShipmentData(this.state.tid, selected);
})
.catch(function (error) {
console.log(error);
});
}
}
}
render() {
return (
<div className="AbusiveEntityDiv">
<select onChange={this.handleChange} value={this.state.entity===null?'':this.state.entity}
style={{width: 100}}>
{ this.state.route.map(value => <option key={value} value={value}>{value}</option>) }
</select>
</div>
);
}
}
Parent component
import React from 'react';
import ReactTable from 'react-table';
import 'react-table/react-table.css';
import Popup from "reactjs-popup";
export default class DetailsTable extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
shipmentDataMap : { },
selectedRow: null,
downloadableAlerts: []
};
this.setState = this.setState.bind(this);
this.handleRowClick = this.handleRowClick.bind(this);
this.handleReassignment = this.handleReassignment.bind(this);
this.handleStatusUpdate = this.handleStatusUpdate.bind(this);
this.generateFilteredArr = this.generateFilteredArr.bind(this);
this.handleDownload = this.handleDownload.bind(this);
this.updateActualEntity = this.updateActualEntity.bind(this);
};
// this portion was updated
componentDidMount() {
fetch('/shipment/all')
.then(res => res.json())
.then(shipmentList => {
var tidToShipmentMap = {};
var totalShipmentCount = shipmentList.length;
var loadedShipmentRoute = 0;
shipmentList.forEach(shipment => {
axios.get('/entity/getRoute', {
params: {
trackingId: shipment.tid
}
})
.then(response => {
let tempRoute = [];
let prev="";
response.data.route.forEach(element => {
if(prev!== "") {
tempRoute.push(prev+"-"+element['node'])
}
tempRoute.push(element['node']);
prev=element['node'];
})
shipment.route = [''].concat(tempRoute);
tidToShipmentMap[shipment.tid] = shipment;
loadedShipmentRoute++;
if (loadedShipmentRoute === totalShipmentCount) {
this.setState({ shipmentDataMap: tidToShipmentMap});
console.log(tidToShipmentMap);
}
})
.catch(function (error) {
console.log(error);
});
});
})
.catch(error => console.log(error));
};
updateActualEntity = (trackingId, updatedEntity) => {
let updatedRecord = this.state.shipmentDataMap[trackingId];
updatedRecord.actualEntity = updatedEntity;
this.setState({shipmentDataMap: this.state.shipmentDataMap});
};
render() {
const TableColumns = [{
Header: 'Actions',
id: 'actionPopupButton',
filterable: false,
style: {'textAlign': 'left'},
Cell: row => (<div><ReassignPopup data={row.original} updateRowFunc={this.handleReassignment} nodeOptions={this.props.nodeOptions}/>
<br/>
<UpdateStatusPopup data={row.original} updateRowFunc={this.handleStatusUpdate} statusOptions={this.props.statusOptions}/>
</div>)
},
{
Header: 'Assigned Node',
headerStyle: {'whiteSpace': 'unset'},
accessor: 'node',
style: {'whiteSpace': 'unset'}
}, {
Header: 'TID',
headerStyle: {'whiteSpace': 'unset'},
accessor: 'tid',
width: 140,
filterMethod: (filter, row) => {
return row[filter.id].startsWith(filter.value)
},
Cell: props => {props.value}
},
{
Header: 'Predicted Entity',
headerStyle: {'whiteSpace': 'unset'},
filterable: false,
accessor: 'predictedEntity',
style: {'whiteSpace': 'unset'},
},
{
Header: 'Feedback',
headerStyle: {'whiteSpace': 'unset'},
filterable: false,
accessor: 'actualEntity',
width: 140,
style: {'whiteSpace': 'unset', overflow: 'visible'},
Cell: row => (<div><AbusiveEntityComponent entity={row.original.actualEntity}
tid={row.original.tid} trackingDetailsId={row.original.trackingDetailsId}
updateActualEntityInShipmentData={this.updateActualEntity}/></div>)
}
return <div>
<CSVLink data={this.state.downloadableAlerts} filename="ShipmentAlerts.csv" className="hidden" ref={(r) => this.csvLink = r} target="_blank"/>
<ReactTable
ref={(r)=>this.reactTable=r}
className='-striped -highlight'
filterable
data={Object.values(this.state.shipmentDataMap)}
//resolveData={data => data.map(row => row)}
columns={TableColumns}
//filtered={this.state.filtered}
filtered={this.generateFilteredArr(this.props.filterMap, this.props.searchParams)}
/*onFilteredChange={(filtered, column, value) => {
this.onFilteredChangeCustom(value, column.id || column.accessor);
}}*/
defaultFilterMethod={(filter, row, column) => {
const id = filter.pivotId || filter.id;
if (typeof filter.value === "object") {
return row[id] !== undefined
? filter.value.indexOf(row[id].toString()) > -1
: true;
} else {
return row[id] !== undefined
? String(row[id]).indexOf(filter.value) > -1
: true;
}
}}
defaultPageSize={10}
//pageSize={10}
previousText='Previous Page'
nextText='Next Page'
noDataText='No intervention alerts found'
style={{
fontSize: "12px",
height: "67.4vh" // Using fixed pixels/limited height will force the table body to overflow and scroll
}}
getTheadFilterProps={() => {return {style: {display: "none" }}}}
getTbodyProps={() => {return {style: {overflowX: "hidden" }}}} //For preventing extra scrollbar in Firefox/Safari
/*
getTrProps={(state, rowInfo) => {
if (rowInfo && rowInfo.row) {
return {
onClick: (e) => {this.handleRowClick(e, rowInfo)},
style: {
//background: rowInfo.index === this.state.selectedRow ? '#00afec' : 'white',
color: rowInfo.index === this.state.selectedRow ? 'blue' : 'black'
}
}
} else {
return {}
}
}
} */
/>
</div>;
}
}

Fabric UI DetailsList control - Dynamically load group items

I'm using grouped DetailsList Fabric UI component in custom SPFx webpart for displaying list data.
I need to load items of the group dynamically from the server by the API call after group expand, but can't find any available group expand callbacks in DetailsList component with available exposed group props (name of the group, etc.) as a method parameter for building a request string. It should look like that:
https://contoso.sharepoint.com/site/_api/web/Lists/getbytitle('ListTitle')/RenderListDataAsStream?#listUrl=&View=&IsGroupRender=TRUE&DrillDown=1&GroupString=%3B%23Exel%20Format%20Files%3B%23%3B%23&
Basically, I want to achieve behavior of the standard modern Document Library webpart existing in Sharepoint 2019. Just need a callback on group expand for update items array. Any other ways to achieve this with DetailsList component?
Code sample of the component (from documentation):
import * as React from 'react';
import {
BaseComponent,
DefaultButton,
DetailsHeader,
DetailsList,
IColumn,
IDetailsHeaderProps,
IDetailsList,
IGroup,
IRenderFunction,
IToggleStyles,
mergeStyles,
Toggle
} from 'office-ui-fabric-react';
const margin = '0 20px 20px 0';
const controlWrapperClass = mergeStyles({
display: 'flex',
flexWrap: 'wrap'
});
const toggleStyles: Partial<IToggleStyles> = {
root: { margin: margin },
label: { marginLeft: 10 }
};
export interface IDetailsListGroupedExampleItem {
key: string;
name: string;
color: string;
}
export interface IDetailsListGroupedExampleState {
items: IDetailsListGroupedExampleItem[];
groups: IGroup[];
showItemIndexInView: boolean;
isCompactMode: boolean;
}
const _blueGroupIndex = 2;
export class DetailsListGroupedExample extends BaseComponent<{}, IDetailsListGroupedExampleState> {
private _root = React.createRef<IDetailsList>();
private _columns: IColumn[];
constructor(props: {}) {
super(props);
this.state = {
items: [
{ key: 'a', name: 'a', color: 'red' },
{ key: 'b', name: 'b', color: 'red' },
{ key: 'c', name: 'c', color: 'blue' },
{ key: 'd', name: 'd', color: 'blue' },
{ key: 'e', name: 'e', color: 'blue' }
],
// This is based on the definition of items
groups: [
{ key: 'groupred0', name: 'Color: "red"', startIndex: 0, count: 2 },
{ key: 'groupgreen2', name: 'Color: "green"', startIndex: 2, count: 0 },
{ key: 'groupblue2', name: 'Color: "blue"', startIndex: 2, count: 3 }
],
showItemIndexInView: false,
isCompactMode: false
};
this._columns = [
{ key: 'name', name: 'Name', fieldName: 'name', minWidth: 100, maxWidth: 200, isResizable: true },
{ key: 'color', name: 'Color', fieldName: 'color', minWidth: 100, maxWidth: 200 }
];
}
public componentWillUnmount() {
if (this.state.showItemIndexInView) {
const itemIndexInView = this._root.current!.getStartItemIndexInView();
alert('first item index that was in view: ' + itemIndexInView);
}
}
public render() {
const { items, groups, isCompactMode } = this.state;
return (
<div>
<div className={controlWrapperClass}>
<DefaultButton onClick={this._addItem} text="Add an item" styles={{ root: { margin: margin } }} />
<Toggle label="Compact mode" inlineLabel checked={isCompactMode} onChange={this._onChangeCompactMode} styles={toggleStyles} />
<Toggle
label="Show index of first item in view when unmounting"
inlineLabel
checked={this.state.showItemIndexInView}
onChange={this._onShowItemIndexInViewChanged}
styles={toggleStyles}
/>
</div>
<DetailsList
componentRef={this._root}
items={items}
groups={groups}
columns={this._columns}
ariaLabelForSelectAllCheckbox="Toggle selection for all items"
ariaLabelForSelectionColumn="Toggle selection"
onRenderDetailsHeader={this._onRenderDetailsHeader}
groupProps={{
showEmptyGroups: true
}}
onRenderItemColumn={this._onRenderColumn}
compact={isCompactMode}
/>
</div>
);
}
private _addItem = (): void => {
const items = this.state.items;
const groups = [...this.state.groups];
groups[_blueGroupIndex].count++;
this.setState(
{
items: items.concat([
{
key: 'item-' + items.length,
name: 'New item ' + items.length,
color: 'blue'
}
]),
groups
},
() => {
if (this._root.current) {
this._root.current.focusIndex(items.length, true);
}
}
);
};
private _onRenderDetailsHeader(props: IDetailsHeaderProps, _defaultRender?: IRenderFunction<IDetailsHeaderProps>) {
return <DetailsHeader {...props} ariaLabelForToggleAllGroupsButton={'Expand collapse groups'} />;
}
private _onRenderColumn(item: IDetailsListGroupedExampleItem, index: number, column: IColumn) {
const value = item && column && column.fieldName ? item[column.fieldName as keyof IDetailsListGroupedExampleItem] || '' : '';
return <div data-is-focusable={true}>{value}</div>;
}
private _onShowItemIndexInViewChanged = (event: React.MouseEvent<HTMLInputElement>, checked: boolean): void => {
this.setState({ showItemIndexInView: checked });
};
private _onChangeCompactMode = (ev: React.MouseEvent<HTMLElement>, checked: boolean): void => {
this.setState({ isCompactMode: checked });
};
}
I was looking for this today also and after checking the source code I figured it out. Use the groupProps.headerPropsto set a callback on group collapsing/expanding
<DetailsList
...
groupProps={{
headerProps: {
onToggleCollapse: this._onGroupToggleCollapse
}
}}
/>
So, the basic logic for this action is (using onToggleCollapse callback):
private _onToggleCollapse(props: IGroupDividerProps): () => void {
...
if (props.group.data.isLoaded === false && props.group.isCollapsed === false && props.group.level > 0) {
...
let data: any = this._getGroupItems(props.group, isLoadAll, {}).then((resp: any) => {
resp.json().then((responseJSON: any) => {
...
updatedItems = this.state.items.map((el: any, i: number) => {
...
});
...
this.setState({
items: [...updatedItems],
groups: [...this.state.groups]
});
});
});
...
}
...
return () => {
props.onToggleCollapse!(props!.group!);
};
}
We need to check for expanding to prevent updates on collapsing of the group.

Resources