I am using Fluent UI DetailsList. In the example the component is implemented as a class component but I am using a functional component.
I am having difficulties in getting the selected items, I assume and think my implementation is incorrect. The problem is I do not get ANY selected items.
export const JobDetails = () => {
const { actions, dispatch, isLoaded, currentTabJobs, activeTabItemKey } = useJobDetailsState()
let history = useHistory();
useEffect(() => {
if (actions && dispatch) {
actions.getJobListDetails()
}
}, [actions, dispatch])
const getSelectionDetails = (): string => {
let selectionCount = selection.getSelectedCount();
switch (selectionCount) {
case 0:
return 'No items selected';
case 1:
return '1 item selected: ' + (selection.getSelection()[0] as any).name;
default:
return `${selectionCount} items selected`;
}
}
const [selectionDetails, setSelectionDetails] = useState({})
const [selection, setSelection] = useState(new Selection({
onSelectionChanged: () => setSelectionDetails(getSelectionDetails())
}))
useEffect(() => {
setSelection(new Selection({
onSelectionChanged: () => setSelectionDetails(getSelectionDetails())
}))
},[selectionDetails])
return (
<div>
<MarqueeSelection selection={selection}>
<DetailsList
items={currentTabJobs}
groups={getGroups()}
columns={_columns}
selection={selection}
selectionPreservedOnEmptyClick={true}
groupProps={{
onRenderHeader: props => {
return (
<GroupHeader
{...props}
selectedItems={selection}
/>
)
},
showEmptyGroups: true
}}
/>
</MarqueeSelection>
</div>
)
}
export default JobDetails;
I might have a more simple answer, this example is for a list with 'SelectionMode.single' activated but I think the principle of getting the selected item remains the same
const [selectedItem, setSelectedItem] = useState<Object | undefined>(undefined)
const selection = new Selection({
onSelectionChanged: () => {
setSelectedItem(selection.getSelection()[0])
}
})
useEffect(() => {
// Do something with the selected item
console.log(selectedItem)
}, [selectedItem])
<DetailsList
columns={columns}
items={items}
selection={selection}
selectionMode={SelectionMode.single}
selectionPreservedOnEmptyClick={true}
setKey="exampleList"
/>
I found a solution to the problem I was having and I had to memorize the details list
What I did:
const [selectedItems, setSelectedItems] = useState<IObjectWithKey[]>();
const selection = useMemo(
() =>
new Selection({
onSelectionChanged: () => {
//console.log('handle selection change',selection.getSelection())
setSelectedItems(selection.getSelection());
},
selectionMode: SelectionMode.multiple,
}),
[]);
const detailsList = useMemo(
() => (
<MarqueeSelection selection={selection}>
<DetailsList
items={currentTabJobs}
groups={getGroups()}
columns={columns}
ariaLabelForSelectAllCheckbox="Toggle selection for all items"
ariaLabelForSelectionColumn="Toggle selection"
checkButtonAriaLabel="Row checkbox"
selection={selection}
selectionPreservedOnEmptyClick={true}
groupProps={{
onRenderHeader: (props) => {
return <GroupHeader {...props} selectedItems={selection} />;
},
showEmptyGroups: true,
}}
onRenderItemColumn={(item, index, column) =>
renderItemColumn(item, index!, column!)
}
/>
</MarqueeSelection>
),
[selection, columns, currentTabJobs, activeTabItemKey]
);
return (
<div>
{detailsList}
</div>
)
Put the selection object in a state.
Example:
...
export const Table: FunctionComponent<TableProps> = props => {
const { items, columns } = props
const { setCopyEnabled } = useCommandCopy()
const { setDeleteEnabled } = useCommandDelete()
const onSelectionChanged = () => {
if (selection.getSelectedCount() === 0) {
setCopyEnabled(false)
setDeleteEnabled(false)
}
else if (selection.getSelectedCount() === 1) {
setCopyEnabled(true)
setDeleteEnabled(true)
}
else {
setCopyEnabled(false)
setDeleteEnabled(true)
}
}
...
const [selection] = useState(new Selection({ onSelectionChanged: onSelectionChanged }))
useEffect(() => {
selection.setAllSelected(false)
}, [selection])
...
return (
<ScrollablePane styles={{
root: {
position: 'fixed',
top: 105, left: 285, right: 20, bottom: 20
},
}}>
<DetailsList
items={items}
columns={columns}
selection={selection}
selectionMode={SelectionMode.multiple}
layoutMode={DetailsListLayoutMode.justified}
constrainMode={ConstrainMode.horizontalConstrained}
...
/>
</ScrollablePane>
)
}
I think the main issue here is onSelectionChanged function is getting called twice, second time with empty data. Reason I found is React useState method re-rendering the data. Solution that worked for me here :
Store value in a normal variable instead of state variable(if you don't want to re-render detailslist after this):
let selectedItem = undefined;
const selection = new Selection({
onSelectionChanged: () => {
selectedItem = selection.getSelection()
// console.log(selectedItem)
// You can use selectedItem value later anywhere you want to
// track your selection.
}
})
<DetailsList
columns={columns}
items={items}
selection={selection}
selectionMode={SelectionMode.multiple}
selectionPreservedOnEmptyClick={true}
setKey="exampleList"
/>
Related
So I have a table with coordinates, and when I click on one particular row it should be highlighted and other rows should be default color.
For now it looks like this:
const TableComponent = () => {
const [active, setActive] = useState(false);
useEffect(() => {
console.log(active);
}, [active]);
return (
<Table
dataSource={dataSource}
columns={columns}
rowClassName={active ? "green" : null}
onRow={(record, rowIndex) => {
return {
onClick: (event) => {
setActive(true);
}, // click row
};
}}
/>
);
};
export default TableComponent;
When I click on one row all of the rows get highlighted, how could I make it only to one row?
You can set the active record, and compare it with the record argument of the rowClassName prop function. If they are the same, then set your custom class name to this row you clicked.
rowClassName prop has function(record, index): string signature, you should always return a string instead of null.
type ID = string | number;
const TableComponent = () => {
const [activeRecord, setActiveRecord] = useState<{ id: ID }>();
console.log(activeRecord);
return (
<Table
dataSource={dataSource}
columns={columns}
rowClassName={(record) => record.id === activeRecord?.id ? "green" : ''}
onRow={(record) => {
return {
onClick: () => {
setActiveRecord(record);
},
};
}}
/>
);
};
export default TableComponent;
antd version: v5.0.5
const App = () => {
const [activeIndex, setActiveIndex] = useState()
return (
<Table
columns={columns}
dataSource={data}
rowClassName={(record, index) => (index === activeIndex ? 'green' : null)}
onRow={(record, rowIndex) => {
return {
onClick: (event) => {
setActiveIndex(rowIndex)
}, // click row
}
}}
/>
)
}
How the memorized callback function works? In some articles I read that the function is recreated if we do not use useCallback. But if it is recreated, should it be different from the prev version? In my code I didn't notice that there was a difference in callback functions.
My question is: Why in both cases, my set size is 1?
from off doc useCallback
Returns a memoized callback.
Pass an inline callback and an array of dependencies. useCallback will
return a memoized version of the callback that only changes if one of
the dependencies has changed. This is useful when passing callbacks to
optimized child components that rely on reference equality to prevent
unnecessary renders (e.g. shouldComponentUpdate).
import { useCallback } from "react";
const dataSource = [
{
id: 1,
model: "Honda",
color: "red",
},
{
id: 2,
model: "Mazda",
color: "yellow",
},
{
id: 3,
model: "Toyota",
color: "green",
},
];
const Car = ({ model, color, set, onCarClick }) => {
const onClick = () => onCarClick(model, color);
set.add(onCarClick);
console.log(set.size);
return (
<div onClick={onClick}>
Model: {model} Color: {color}
</div>
);
};
const CarsCallback = ({ cars, set }) => {
const onCarClick = (model, color) => {
console.log(model, color);
};
console.log("CarsCallback");
return (
<>
{cars.map((car) => {
return (
<Car
key={car.id}
set={set}
{...car}
onCarClick={onCarClick}
/>
);
})}
</>
);
};
const CarsUseCallback = ({ cars, set }) => {
const onCarClick = useCallback((model, color) => {
console.log(model, color);
}, []);
console.log("CarsUseCallback");
return (
<>
{cars.map((car) => {
return (
<Car
key={car.id}
{...car}
set={set}
onCarClick={onCarClick}
/>
);
})}
</>
);
};
export default function App() {
return (
<div className="App">
<CarsCallback cars={dataSource} set={new Set()} />
<CarsUseCallback cars={dataSource} set={new Set()} />
</div>
);
}
Because CarsUseCallback and CarsCallback was triggered once.
We can see the only one log of CarsUseCallback and CarsCallback.
If we re-render the CarsUseCallback and CarsCallback, we can see the size is 1 and 2.
const CarsCallback = ({ cars, set }) => {
const [count, setCount] = useState(1);
console.log('CarsCallback');
useEffect(() => {
setCount(2);
}, []);
// ...
};
const CarsUseCallback = ({ cars, set }) => {
const [count, setCount] = useState(1);
console.log('CarsUseCallback');
useEffect(() => {
setCount(2);
}, []);
// ...
}
I'm using function component to create a MUI dataGrid, and trying to add a button in a column, and I have a onRowClick function to open a side pane when user clicking row. The problem is, once I click row, react will report error:
Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
Here is the code:
const openViewPane = (params: GridRowParams, e): void => {
setRightSlidePlaneContent(
<ViewAccountPane
close={closeForm}
params={params}
/>,
);
setRightSlidePlaneOpen(true);
};
const formatDates = (columns): GridColDef[] => {
return columns;
};
const addTooltipsToData = (columns: GridColDef[]): GridColDef[] => {
console.log('render tool bar called');
return columns.map((column) => {
const { description, field, headerName } = column;
console.log('inside map');
if (field === ID) {
console.log('直接return');
return column;
}
return {
...column,
renderCell: (): JSX.Element => {
console.log('render run');
return (
<Tooltip arrow title={description || ''} >
<span className={classes.headerCell}>{headerName}</span>
</Tooltip>
);
},
};
});
};
const formatColumns = (columns: GridColDef[]): GridColDef[] => {
const dateFormatted = formatDates(columns);
return addTooltipsToData(dateFormatted);
};
console.log('generic table rendered');
return (
<MuiThemeProvider theme={theme}>
<DataGrid
columns={formatColumns(columns)}
rows={rows}
autoHeight
className={classes.table}
components={{
Toolbar: CustomToolbar,
}}
density={GridDensityTypes.Compact}
filterMode={tableMode}
hideFooterSelectedRowCount
loading={loading}
onFilterModelChange={handleFilterChange}
onSortModelChange={handleSortChange}
sortModel={sortModel}
sortingMode={tableMode}
onRowClick={openViewPane}
/>
</MuiThemeProvider>
);
However, if I change the renderCell to renderHeader, it will work fine.
setRightSlidePlaneContent
setRightSlidePlaneOpen
Above are two state passed by parent component in props. it will open a slide pane.
After I comment setRightSliePlaneOpen, it will work well. But no slide pane show.
Please help me slove it. Or do you know how can I add a button in column not using renderCell?
const PageFrame: FC<IProps> = (props: IProps) => {
const classes = useStyles();
const dispatch = useAppDispatch();
const { Component, userInfo } = props;
const [navBarOpen, setNavBarOpen] = useState(false);
const [rightSlidePlaneOpen, setRightSlidePlaneOpen] = useState(false);
const [rightSlidePlaneContent, setRightSlidePlaneContent] = useState(
<Fragment></Fragment>,
);
const [rightSlidePlaneWidthLarge, setRightSlidePlaneWidthLarge] = useState(
false,
);
useEffect(() => {
dispatch({
type: `${GET_USER_LOGIN_INFO}_${REQUEST}`,
payload: {
empId: userInfo.empId,
auth: { domain: 'GENERAL_USER', actionType: 'GENERAL_USER', action: 'VIEW', empId: userInfo.empId},
},
meta: { remote: true },
});
}, []);
return (
<div className={classes.root}>
<HeaderBar
navBarOpen={navBarOpen}
toggleNavBarOpen={setNavBarOpen}
/>
<NavigationBar open={navBarOpen} toggleOpen={setNavBarOpen} />
<Component
setRightSlidePlaneContent={setRightSlidePlaneContent}
setRightSlidePlaneOpen={setRightSlidePlaneOpen}
setRightSlidePlaneWidthLarge={setRightSlidePlaneWidthLarge}
/>
<PersistentDrawerRight
content={rightSlidePlaneContent}
open={rightSlidePlaneOpen}
rspLarge={rightSlidePlaneWidthLarge}
/>
</div>
);
};
export default PageFrame;
The component that calls setRightSidePlaneOpen
interface IProps {
setRightSlidePlaneContent: React.Dispatch<React.SetStateAction<JSX.Element>>;
setRightSlidePlaneOpen: React.Dispatch<React.SetStateAction<boolean>>;
setRightSlidePlaneWidthLarge: React.Dispatch<SetStateAction<boolean>>;
}
const TagDashboard = (props: IProps): JSX.Element => {
const { setRightSlidePlaneContent, setRightSlidePlaneOpen, setRightSlidePlaneWidthLarge } = props;
const employeeId = useAppSelector((store) => store.userInfo.info.employeeNumber);
const rows = useAppSelector((state) => state.tag.rows);
const accountId = useAppSelector(store => store.userInfo.accountId);
const updateContent = useAppSelector(state => state.tag.updateContent);
const numOfUpdates = useAppSelector(state => state.tag.numOfUpdates);
const dispatch = useAppDispatch();
const closeAddForm = (): void => {
setRightSlidePlaneContent(<Fragment />);
setRightSlidePlaneOpen(false);
};
const openAddForm = (): void => {
setRightSlidePlaneContent(
<AddForm
category={'tag'}
close={closeAddForm}
title={ADD_FORM_TITLE}
createFunction={createTag}
/>);
setRightSlidePlaneOpen(true);
};
const closeForm = (): void => {
setRightSlidePlaneContent(<Fragment />);
setRightSlidePlaneOpen(false);
setRightSlidePlaneWidthLarge(false);
};
const openViewPane = (params: GridRowParams, e): void => {
setRightSlidePlaneContent(
<ViewAccountPane
close={closeForm}
params={params}
/>,
);
setRightSlidePlaneOpen(true);
setRightSlidePlaneWidthLarge(true);
};
// to the RSP.
return (
<GenericDashboard
addFunction={openAddForm}
description={DESCRIPTION}
title={TITLE}
columns={columns}
handleRowClick={openViewPane}
rows={rows}
numOfUpdates={numOfUpdates}
updateContent={updateContent}
/>
);
};
This is the component of the right slide pane
const { content, open, rspLarge } = props;
const classes = useStyles();
const drawerClass = rspLarge ? classes.drawerLarge : classes.drawer;
const drawerPaperClass = rspLarge ? classes.drawerPaperLarge : classes.drawerPaper;
return (
<div className={classes.root}>
<CssBaseline />
<Drawer
className={drawerClass}
variant='temporary'
anchor='right'
open={open}
classes={{
paper: drawerPaperClass,
}}
>
<Fragment>{content}</Fragment>
</Drawer>
</div>
);
how I can update price value when update quantity value automatically ??
page design
interface ui
print values on the console:
print values on the console:
This sentence needs to be modified
{quantity[initQ] == 1 ? data.price : initP[initQ]}
i use setState to save multiple values
export default function Contaner({ setPressed, getPressed }) {
const [products, setProducts] = useState([]);
const [layout, setLayout] = useState('list');
let initQ = 1;
const [initP,setInitP] = useState({ [initQ]: 1 }) ;
const [quantity, setQuantity] = useState({ [initQ]: 1 });
function checkQuantity(e, data) {
if (e.value <= data.quantity) {
initQ = data.name;
setQuantity({ ...quantity, [data.name]: e.value});
setInitP( { ...quantity, [data.name]: data.price * e.value});
console.log(initP );
setCart(current => [...current, data.name]);
}
else {
showError();
}
}
const renderListItem = (data) => {
return (
<div style={{ display: "flex" }}>
<button className="button_color" onClick={() => removeItem(data)}>
<i className="pi pi-trash"></i>
</button>
<h6>{quantity[initQ] == 1 ? data.price : initP[initQ] }</h6>
<InputNumber id="stacked" showButtons min={1} value={quantity[initQ]}
onValueChange={(e) => checkQuantity(e, data)} />
<InputText disabled={true} value={"₪ " + data.price} />
<h6>{data.name}</h6>
</div>
);
}
const itemTemplate = (product, layout) => {
if (!product) {
return <></>;
}
if (layout === 'list') {
return renderListItem(product);
}
}
return(
<DataView value={products} layout={layout} itemTemplate={itemTemplate} rows={1} />
);
}
I am a beginner with javscript So i will be thankful for explanation.
{isolate_list.map((row) => {
return (
<FormControlLabel
control={
<Checkbox
color="primary"
checked={!!checked}
onChange={toggleCheckbox}
name="checkedA"
>
{" "}
</Checkbox>
}
label={row.isolatename}
>
{""}
</FormControlLabel>
);
})}
and i have this button
<Button
onClick={selectall}
style={{ margin: 50 }}
variant="outlined"
label="SELECT ALL ISOLATES"
>
SELECT ALL ISOLATES
</Button>
Can anyone help how can i use the button to select all checkboxes and in the same time i can select every checkbox alone by clicking on it?
I beginn with this part but i am not sure
const [checked, setChecked] = React.useState(true);
const toggleCheckbox = (event) => {
setChecked(event.target.checked);
};
You should hold checkbox value's in the and give the state value as a property to each. For example
<Checkbox
color="primary"
onChange={toggleCheckbox}
name="checkedA"
value={checked}
>
And then in the onClick function
setChecked();
The simplest implementations(without any form manager):
Declare state to store our checked ids array.
const [checkedIds, setCheckedIds] = useState([]);
implement handler.
const handleCheck = useCallback((id) => {
return () => {
setCheckedIds(prevIds => prevIds.includes(id) ? prevIds.filter(item => item !== id) : [...prevIds, id]);
};
}, []);
render our checkboxes and apply handler.
list.map(({ id, isolatename }) => (
<FormControlLabel
key={id}
control={
<Checkbox
color="primary"
checked={checkedIds.includes(id)}
onChange={handleCheck(id)}
name={`checkbox_${id}`}
/>
}
label={isolatename}
/>)
))
ps. in case if <Checkbox/> props 'onChange' returns callback like this (isChecked: boolean) => {} we can simplify (2) step.
const handleCheck = useCallback(id => {
return isChecked => {
setCheckedIds(prevIds => isChecked ? prevIds.filter(item => item == id) : [...prevIds, id]);
};
}, []);
You may remember that it is React JS and not only JS that we are talking about.
In React you want to control data in the way of a state. There are a lot of ways to do so with check boxes, I'm contributing with one that you can see in the code snippet below:
import React, {useState} from "react";
export default function CheckBoxesControllers() {
const [checkboxes, setCheckboxes] = useState(() => [
{ id: "0", checked: false },
{ id: "1", checked: false },
{ id: "2", checked: false },
]);
const handleUpdate = (event) => {
const { target: {id, checked} } = event;
setCheckboxes(currentState => {
const notToBeUpdated = currentState.filter(input => input.id !== id);
return [
...notToBeUpdated,
{ id, checked }
]
});
}
function toggleSelectAll() {
setCheckboxes(currentState => currentState.map(checkbox => ({...checkbox, checked: !checkbox.checked})));
}
return (
<>
{checkboxes?.length ? (
checkboxes.map((checkbox, index) => {
return (
<input
checked={checkbox.checked}
id={checkbox.id}
key={index}
type="checkbox"
onChange={event => handleUpdate(event)}
/>
);
})
) : <></>}
<button onClick={toggleSelectAll}>Toggle Select All</button>
</>
)
}
This code is meant to serve you as an example of how to work properly with react state in the hook way, but there are other way, as you can see in the Documentation