How to set checked on item in DetailsList control - reactjs

I use DetailsList component from office-ui-fabric-react library:
import {DetailsList} from 'office-ui-fabric-react/lib/DetailsList';
render () {
const item = [
{value: 'one'},
{value: 'two'}
]
return (
<DetailsList
checkboxVisibility={CheckboxVisibility.always}
items={items}
selection={selection}
/>
}
How to set checked for item with value `two?

Noticed you passed a selection to DetailsList. There's a few methods in selection to do that, including:
setAllSelected(isAllSelected: boolean)
setKeySelected(key: string, isSelected: boolean, shouldAnchor: boolean)
setIndexSelected(index: number, isSelected: boolean, shouldAnchor: boolean)
In your case, you can give each value a key. And then call setKeySelected somewhere (for example, componentDidMount) to tell DetailsList to select specific items.

I looked everywhere and could never find the answer. To set selected items basically on page load do the following:
<DetailsList
columns={columns}
items={getItems()}
selection={getSelectedItems()}
/>
const getItems = () => {
const items: any[] = [];
itemList.map((item: any, i) => {
items.push({
key: i.toString(),
Name: item.Name,
Description: item.Description
});
});
return [];
};
const getSelectedItems = (): ISelection => {
const selection: Selection = new Selection();
const items = getItems();
selection.setItems(items);
selectedItemsList.map((selectedItem:
any, s) => {
selection.setKeySelected(s.toString(), true, false);
});
return selection;
};

Simply call selection.setAllSelected(true) inside useEffect if you want all the list items to be selected by default.
const selection = new Selection();
useEffect(() => {
selection.setAllSelected(true)
}, [selection]);

React Typescript (tsx) Sample.
import { DetailsList, ISelection, Selection } from '#fluentui/react/lib/DetailsList';
// ...
export const Component = () => {
const getSelectionDetails = (): string => {
const selectionCount = selection.getSelection().length;
return selectionCount
? `${selectionCount} items selected: ${selection.getSelection().map( (i: any) => i.key ).join('; ')}`
: 'No items selected';
}
const createSelection = () => {
return new Selection({
onSelectionChanged: () => setSelectionDetails(getSelectionDetails()),
getKey: (item: any) => item.key
});
};
const [selection, setSelection] = React.useState(createSelection());
const [selectionDetails, setSelectionDetails] = React.useState('');
return <>
<DetailsList
// ...
selection={selection}
selectionMode={SelectionMode.multiple}
// ...
/>
</>;
}

Related

the way to use setState with object

import React, {useState} from "react";
const SideListItem = () => {
const [showItem, setShowItem] = useState([
{id: "List A", clicked: true},
{id: "List B", clicked: true},
{id: "List C", clicked: true},
]);
const clickList = () => {
const value = showItem[0].clicked;
setShowItem(() => {
const boolValue = value? false: value === true;
return boolValue;
});
return console.log(value);
};
I want to make the next process below.
when I click a button, the value of state is chaged.
=> if it is "true", then it changed in "false". And if "false", then "true".
But, my code didn't work... When I used state with number, string, boolean, It worked.
Is there a way to use state with object?
Thank you in advance!
I tried this code.
const [clicked, setClicked] = useState(false);
const clickList = () => setClicked(!clicked);
But, I want to use state with object.
You can do it like this and add more statements if needed:
const clickList = () => {
setShowItem(prevState => {
return prevState.map(item => {
if (item.id === "List A") {
return {...item, clicked: !item.clicked};
}
return item;
});
});
};
But it's better to get the id as param like this:
const clickList = (id) => {
setShowItem(prevState => {
return prevState.map(item => {
if (item.id === id) {
return {...item, clicked: !item.clicked};
}
return item;
});
});
};
You can handle it in this way :
const clickList = () => {
setShowItem(prevState => {prevState.map(item=>({...item,clicked:!item.clicked})));
};
You can transform the previous state of the array to one where the clicked property of the clicked item is toggled. Since you didn't provide any html I added some example html, swap it out for your own markup and use the onClick event as such:
const { useCallback, useState } = React;
const SideListItem = () => {
const [showItem, setShowItem] = useState([
{id: "List A", clicked: true},
{id: "List B", clicked: true},
{id: "List C", clicked: true},
]);
const handleClick = useCallback(id => {
setShowItem(p => p.map(item => {
if (item.id === id) {
return {
...item,
clicked: !item.clicked
}
}
return item;
}));
}, []);
return showItem.map(item =>
<div key={item.id} onClick={() => handleClick(item.id)}>
Clicked: {item.clicked ? "yes" : "no"}
</div>
);
}
ReactDOM.createRoot(
document.getElementById("root")
).render(
<SideListItem />
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>

Component not render the newest value?

I'm getting used to with redux. My problem is the itemList correctly render the latest value but the value of Checkbox which is from hook state not get the latest value. It should be checked for all item list but it is not. Although I console.log the values in the map func, it still get the latest values and the find func is correct.
export default function Component(props) {
const dispatch = useDispatch();
const { itemList } = useSelector((state) => state.AllCourses);
const [values, setValues] = useState({
all: true,
items: []
});
useEffect(() => {
dispatch(
someActions.getItemList(payload)
); //this will get latest itemList
}, []);
useEffect(() => {
if (itemList.length) {
const newValues = {
all: true,
items: itemList.map((item) => ({
select: true,
id: item.id,
})),
};
setValues(newValues);
}
}, [itemList]);
return (
<Box ml={4}>
{ itemList?.map((item) => {
return (
<Box key={item.id}>
<Checkbox
name={item.name}
value={values?.items?.find((itemVal) => item.id === itemVal.id)?.select}
/>
</Box>
);
})}
</Box>
);
}
`
Tried several solutions but still not correctly
It seems like you are using Material UI. For checkbox component you need to set the checked prop.
import Checkbox from '#mui/material/Checkbox';
const label = { inputProps: { 'aria-label': 'Checkbox demo' } };
export default function Checkboxes() {
return (
<div>
<Checkbox {...label} defaultChecked />
<Checkbox {...label} />
<Checkbox {...label} disabled />
<Checkbox {...label} disabled checked />
</div>
);
}
If you use html input tag with type checkbox, then again you have to set the checked attribute accordingly see below.
<label for="vehicle2"> I have a car</label><br>
<input type="checkbox" name="vehicle3" value="Boat" checked>
And lastly, you don't need a local state in your example and you can remove
const [values, setValues] = useState({
all: true,
items: []
});
and
useEffect(() => {
if (itemList.length) {
const newValues = {
all: true,
items: itemList.map((item) => ({
select: true,
id: item.id,
})),
};
setValues(newValues);
}
}, [itemList]);
and replace with
const values = {
all: true,
items: itemList && itemList.length ? itemList.map((item) => ({
select: true,
id: item.id,
})) : [],
};

React issues with reading a value in a context

I have this context:
FilterProvider.tsx
import React, { createContext, useRef } from "react";
interface IFiltersContext {
filters: { [key: string]: any };
updateFilter: (name: string, value: any) => void;
addFilter: (name: string, value: any) => void;
clearFilter: (name: string) => void;
}
type FilterContextProps = {
initialFilters?: any;
onFilterChange: (values: any) => void;
};
export const FiltersContext = createContext<IFiltersContext>({
filters: {},
updateFilter: () => {},
addFilter: () => {},
clearFilter: () => {}
});
export const FiltersProvider: React.FC<FilterContextProps> = ({
children,
onFilterChange,
initialFilters = {}
}) => {
const filters = useRef(initialFilters);
const updateFilter = (name: string, value: any) => {
addFilter(name, value);
onFilterChange(filters.current);
};
const addFilter = (name: string, value: any) => {
filters.current = {
...filters.current,
[name]: value
};
};
const clearFilter = (name: string) => {
if (filters.current[name] !== null && filters.current[name] !== undefined) {
updateFilter(name, null);
}
};
return (
<FiltersContext.Provider
value={{ filters, updateFilter, addFilter, clearFilter }}
>
{children}
</FiltersContext.Provider>
);
};
And to be able to use this functions I use it as follows
<FiltersProvider onFilterChange={function (values: any): void {
console.log("Function not implemented.");
} }>
<PrettyTable
tableName="table_publications"
overlayFilter={
<>
<CountryFilter />
</>
}
{...tableData}
/>
</FiltersProvider>
Now inside PrettyTable I have the following : (NOTE1****)
const { showDialog } = useDialog();
const { filters, addFilter } = useContext(FiltersContext);
console.log(filters?.current) //always "somename", "test" , only the function call in Confirm will save things. THe one inside COuntryFIlter (See below) wont
function showFilterSelector() {
showDialog({
title: "Filter Columns",
message: <DialogContent />,
cancel: {
action: () => console.log("cancelled"),
message: "Reset Filters"
},
confirm: {
action: () => addFilter("somename", "test"), //I can see this calling addFilter, and after that "filters.current" has its value.
message: "Apply Filters"
},
align: "start",
width: "100%",
height: "100%",
wide: true
});
}
useEffect(() => {
debugger;
},[filters])
const DialogContent = () => {
return (
<Grid
columns={["flex", "flex", "flex"]}
justifyContent={"around"}
gap="small"
>
{props.overlayFilter} --> Prop found in the first code snippet, content of that component is in the last code snippet (below)
</Grid>
);
};
In the code above, im able to see the confirm action calling the function inside the provider, and it works just fine
But props.overlayFIlter contain the following:
Which is also using the same context inside
export const CountryFilter = ({...}) => {
const { filters, addFilter, updateFilter, clearFilter } = useContext(FiltersContext);
return (
<SelectComponent
onChange={i => {
addFilter("filter1","x") //This one is also being called supposedly in the context, but later I cant read it anywhere
}} /> )
But the above function despite calling the same context, the added data wont be able to be read in (NOTE1*** code snippet). I will only see the filter registered in the confirm action ("somename", "test")
What am I dont wrong? Am I using the contexts wrong?

React hooks- how to set options in dropdown with a variable

I'm working in a project where I'm trying to set the options of a dropdown with an array of objects that I'm getting from a request to an API.
The thing is that when I'm setting the options (key, value, text) with a map of that variable, it appears an error. So I think that way is not correct to what I'm doing.
Can you help me to know what to do in this case?
Here is my code:
import { Dropdown } from 'semantic-ui-react';
type formProps = {
funcionCierre: any
carrera: any;
nombre1: any;
}
const Estudiantes: React.FC<formProps> = (props: formProps) => {
const [area, setArea] = useState<any[]>([]);
useEffect(() => {
console.log(props.carrera);
axios.get('http://localhost:8003/skill?carrera_id=' + props.carrera + '&tipo_id=1')
.then(result => {
console.log(result);
setArea(result.data); //here is where i'm capturing my array of options
console.log(area);
}
).catch(error => {
console.log(error);
});
}, [area.length]);
return (
<Dropdown
placeholder='Area'
search
selection
options={area.map(ar => (
key: ar.skil_id, //here is where i'm trying to set the options
value: ar.skill_id,
text: ar.nombre
))}
/>)
Thanks in advance.
You are missing {} from your area.map(ar => (...)) call. It should be area.map(ar => ({...}))
const Dropdown = (props) => <div>{JSON.stringify(props.options)}</div>;
const Estudiantes = (props) => {
const [area, setArea] = React.useState([]);
React.useEffect(() => {
Promise.resolve([{skill_id: 1, nombre: 'one'}, {skill_id: 2, nombre: 'two'}])
.then(result => setArea(result))
}, [area.length]);
return (
<Dropdown
placeholder='Area'
options={area.map(ar => ({
key: ar.skil_id,
value: ar.skill_id,
text: ar.nombre
}))}
/>
)
}
ReactDOM.render(<Estudiantes />, document.getElementById("app"));
<script src="https://unpkg.com/react#16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.production.min.js"></script>
<div id="app"></div>

Getting selected items in Fluent UI DetailsList

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"
/>

Resources