How to clear Multiselect Dropdown in Semantic UI React using a button? - javascript

I have a Semantic UI React Multiselect Dropdown inside a React functional component and want to have buttons inside the menu, as such (still need to center them...)
How can I clear the selected values using the 'Clear' button?
I am able to clear the selection using the 'x' icon, but that's built into the component.
<Dropdown
search
multiple
selection
clearable
closeOnSelectionChange={false}
options={filterInitialSuggestions()}
className='selectDropdown'
header={dropdownButtons()}
/>
const dropdownButtons = () => {
return (
<div>
<Button positive size='mini'>
Save
</Button>
<Button grey size='mini' onClick={() => console.log('I want to reset the multi select dropdown')}>
Clear
</Button>
<Divider />
</div>
);
};

'Save' and 'Clear' buttons with React useState(). In Reactjs you don't need to use DOM queryselector.
https://codesandbox.io/s/white-leftpad-q6re3?file=/src/Fun.jsx

import React, { Component } from 'react';
import { Dropdown } from 'semantic-ui-react';
const options = [
{ key: 1, text: 'Choice 1', value: 1 },
{ key: 2, text: 'Choice 2', value: 2 },
{ key: 3, text: 'Choice 3', value: 3 },
{ key: 4, text: 'Choice 4', value: 4 },
{ key: 5, text: 'Choice 5', value: 5 },
{ key: 6, text: 'Choice 6', value: 6 },
]
class Example extends Component {
state = {
dropval: []
}
onDropChange = (e, { value }) => {
this.setState(
(prevState) => { return { ...prevState, dropval: value } },
// () => console.log(this.state)
)
}
render() {
return (
<div>
<Dropdown
search
multiple
selection
clearable
closeOnSelectionChange={false}
options={options}
className='selectDropdown'
onChange={this.onDropChange}
value={this.state.dropval}
style={{ width: 300 }}
/>
</div>
);
}
}
export default Example;

I figured out how to solve this issue. I'm not sure if this is the best way, but it seems to work decently.
const dropdownButtons = () => {
return (
<>
<div className='dropdown-saveButton'>
<Button
positive
size='mini'
onClick={() => {
saveValues();
}}
>
Save
</Button>
<Button size='mini' onClick={clearDropdown}>
Clear
</Button>
</div>
<Divider inverted />
</>
);
};
const clearDropdown = e => {
var element = dropdownRef.current.querySelector('[aria-selected="true"]');
if (element) {
dropdownRef.current.querySelector('.clear')?.click();
}
};
<Dropdown
multiple
selection
fluid
clearable
header={dropdownButtons} />

Related

Using a Prop to populate an array - React useState

I have 3 dropdowns, each which controls state. When this dropdown is selected it will set the target and send it to redux. For example.
const [interviewStep1, setinterviewStep1] = useState('Phone Screening')
const [interviewStep2, setinterviewStep2] = useState('Formal Interview')
const [interviewStep3, setinterviewStep3] = useState('Reference Check')
This is sent to redux in this manner.
<Dropdown_Content>
{interviewStageSelection.map((option) => (
<Dropdown_Item
key={option}
onClick={(e) => {
setinterviewStep1(option)
setisActive(!isActive)
console.log(interviewStep1)
setisActive(false)
updateInterview1(dispatch, option)
}}
>
<Typography variant="subtitle5" color="black" sx={{ "&:hover": { color: "white" } }}>{option}</Typography>
</Dropdown_Item>
))}
</Dropdown_Content>
I then pass this state as props into my next component.
export default function JobPostInterviewVerticalStepper(interviewStep1, interviewStep2, interviewStep3)
this does come through, but then I want to display in my array. How do I use these props?
const steps = [
{
label: 'Phone Screening',
//I WANT A interviewStep1 here!
},
{
label: 'Formal Interview',
},
{
label: 'Reference Check',
},
{
label: 'Offer',
},
];

Getting content of currently active Text component wrapped inside popover of antd

I am using antd components for my react app. I have a Text component wrapped inside of Popover component. Now in my case this Popover is applied to one particular column of table, i.e. every row-element in that column has a Popover component rendered for it upon mouse hovering.
title: "Name",
dataIndex: "name",
key: "name-key",
sortType: "string",
sortDirections: ["descend", "ascend"],
sorter: (a, b) => a.name.length - b.name.length,
render: (text, record) => (
<Popover>
<Text onMouseOver={handleOnMouseOverCommitId}> {name} </Text>
</Popover>
)
I want to get hold of the row-element's value, the one contained by the above Text component whenever I hover over it. In this case the value denoted by {name} above.
I tried getting it with e.target.value via onMouseOver event, but it returned undefined.
I think I get the reason behind it, because the event.target returns an html node of type <span>.
With a normal div element e.target.value has worked in the past for me. But doing the same thing with a predefined component like antd's Text seems a bit trickier.
Just to elaborate, the Popover has two buttons and based on which button user clicks, I need to render some other components, something like an overlay component.
But in order to do that I would also need to get hold of the text value which originally triggered the Popover.
Below is the code(most of the things removed for preciseness).
record.name is what I ultimately need to capture.
<Popover
content={
<>
<Space>
<Button onClick={showSomeOverlayPaneForName}>
{"View Details for record.name"}
</Button>
<Button href={"https://abc.xyz.com/" + record.role}>
{"View Role Details"}
</Button>
</Space>
</>
}
trigger={"hover"}
>
<Text style={{"color": blue.primary}} copyable={true} onMouseOver={handleOnMouseOverName}>{record.name}</Text>
</Popover>
The handleOnMouseOverName function(which doesn't work anyway) :
const handleOnMouseOverName = (e) => {
//console.log("e.target.value :--- ", e.target.value);
setCurrentActiveName(e.target.value)
}
And once my currentActiveName variable is set(via useState), I use that value inside my function showSomeOverlayPaneForName
const showSomeOverlayPaneForName = (e) => {
axios
.get(
`some-url`,
{
params: {name: currentActiveName}
}
)
.then((response) => {
setData(response.data);
}).catch(reason => {
//xyz
});
}
You need to pass on the record of the enclosing render function to the handleOnMouseOverName function.
Check the following example
import React from 'react';
import 'antd/dist/antd.css';
import './index.css';
import { Space, Table, Button, Popover } from 'antd';
const App = () => {
const data = [
{
key: '1',
name: 'John Brown',
address: 'New York No. 1 Lake Park',
role: 'admin',
},
{
key: '2',
name: 'Jim Green',
address: 'London No. 1 Lake Park',
role: 'user',
},
{
key: '3',
name: 'Joe Black',
address: 'Sidney No. 1 Lake Park',
role: 'manager',
},
];
const columns = [
{
title: 'Name',
dataIndex: 'name',
key: 'name',
render: (name, record) => {
const content = (
<>
<Space>
<Button
onClick={() => {
viewDetail(record);
}}
>
{'View Details for ' + record.name}
</Button>
<Button href={'https://abc.xyz.com/' + record.role}>
{'View Role Details'}
</Button>
</Space>
</>
);
return (
<>
<Popover content={content} title="Details">
<div
onMouseOver={() => {
handleOnMouseOverName(record);
}}
>
{name}
</div>
</Popover>
</>
);
},
},
{
title: 'Address',
dataIndex: 'address',
key: 'address',
},
];
const handleOnMouseOverName = (record) => {
console.log(record);
};
const viewDetail = (record) => {
console.log(record);
};
return <Table columns={columns} dataSource={data} />;
};
export default App;
Output:
I hope this helps.
From antd docs: https://ant.design/components/popover/#header
Apparently you're supposed to render the <Popover/> with a content={content}-prop
For example
const content = <div>Content to render under title</div>
const App = () => {
const value = "Text to hover";
return (
<Popover content={content} title="Title">
<Text>{value}</Text>
</Popover>
)
}

SetState inside useEffect is causing side effects on select input functionality

Each select menu comes with a help text inside a box. Similar to a tooltip. User can close them when clicking 'close button' or clicking outside.
My solution works and they are being closed each time you click outside them.
The problem is that setState inside the useEffect has a side effect on the select menus.
The issue is when I close the info box using the 'close button' or click inside the info box. After I close it with the button or click inside it, if I try to change an option, I see the options flickering and I can't change selection, it would only work the second time.
Here is my code: https://stackblitz.com/edit/react-61rzle?file=src%2FSelect.js
export default function Select() {
const selectMenus = [
{
Label: 'Select 1',
Name: 'select1',
DefaultValue: '1',
HelpText: 'Help text',
Id: 'select_1',
Options: [
{
Value: '0',
Text: 'All age groups',
},
{
Value: '1',
Text: 'Less than 35',
},
{
Value: '2',
Text: '35 - 37 yrs',
},
{
Value: '3',
Text: '38 - 39 yrs',
},
{
Value: '4',
Text: '40 - 42 yrs',
},
{
Value: '5',
Text: '43 - 44 yrs',
},
{
Value: '6',
Text: '45 yrs +',
},
],
},
{
Label: 'Select 2',
Name: 'select2',
DefaultValue: '0',
HelpText: 'Help text',
Id: 'select_2',
Options: [
{
Value: '0',
Text: 'All',
},
{
Value: '1',
Text: 'Less than 35',
},
{
Value: '2',
Text: '43 - 44 yrs',
},
],
},
];
const [value, setValue] = useState({
select1: '',
select2: '',
});
// help texts setup
const initialVisibleHelpTexts = {
info0: false,
info1: false,
info2: false,
};
const [visibleHelpText, setVisibleHelpText] = useState(
initialVisibleHelpTexts
);
const showHelpText = (e, key) => {
e.preventDefault();
e.stopPropagation();
setVisibleHelpText({ ...initialVisibleHelpTexts, ...{ [key]: true } });
};
const hideHelpText = (e, key) => {
e.preventDefault();
e.stopPropagation();
setVisibleHelpText({ ...visibleHelpText, ...{ [key]: false } });
};
// close info on click outside
useEffect(() => {
document.addEventListener('click', function (e) {
e.preventDefault();
e.stopPropagation();
if (
e.target.parentNode.className !== 'info__content' &&
e.target.parentNode.className !== 'info__content-header-text' &&
e.target.parentNode.className !== 'info__content-header'
) {
setVisibleHelpText(initialVisibleHelpTexts);
}
});
}, []);
const handleOnChange = (e) => {
const valueSelected = e.target.value;
setValue({
...value,
[e.target.name]: valueSelected,
});
};
return (
<form>
{selectMenus.length > 0 && (
<div className="selectors-container">
{selectMenus.map((select, i) => (
<div className="select" key={uuid()}>
<div className="select__label-container">
<div className="select__title">
<label className="select__label" htmlFor={select.Id}>
{select.Label}
</label>
<button
className="select__info"
onClick={(e) => {
showHelpText(e, `info${i}`);
}}
>
Show info
</button>
</div>
{visibleHelpText[`info${i}`] && (
<div className="info">
<div className="info__content">
<div className="info__content-header">
<span className="info__content-header-title">
{select.Label}
</span>
<button
onClick={(e) => {
hideHelpText(e, `info${i}`);
}}
>
Close info
</button>
</div>
<div className="info__content-header-text">
{select.HelpText}
</div>
</div>
</div>
)}
</div>
<div className="select__menu-btn-container">
<div className="select__container">
<select
name={select.Name}
id={select.Id}
value={value[`${select.Name}`]}
onChange={handleOnChange}
>
{select.Options.map((option) => (
<option value={option.Value} key={uuid()}>
{option.Text}
</option>
))}
</select>
</div>
</div>
</div>
))}
</div>
)}
</form>
);
}
The flickering happens because you have one huge component that re-renders each time you toggle the visibility of the info text. As soon as you click on the select, the whole component gets re-rendered which leads to the select being closed right away.
To solve this, you have to prevent the whole component from re-rendering. Separate it into smaller chunks, which can be rerendered separately.
Here is a simplified example to show how to isolate the info section into a self-managed component.
function InfoSection({ select }) {
const [isVisible, setIsVisible] = useState(false);
return (
<div className="select__label-container">
<div className="select__title">
<label className="select__label" htmlFor={select.Id}>
{select.Label}
</label>
<button
className="select__info"
onClick={(e) => {
setIsVisible(true);
}}
>
Show info
</button>
</div>
{isVisible && <InfoText setIsVisible={setIsVisible} />}
</div>
);
}
function InfoText({ setIsVisible }) {
function handleCLickOutside(e) {
setIsVisible(false);
}
useEffect(() => {
document.addEventListener('click', handleCLickOutside);
//this will remove the event listener, when the component gets unmounted. This is important!
return () => document.removeEventListener('click', handleCLickOutside);
}, []);
return (
<div className="info">
<div className="info__content">
<div className="info__content-header">
<span className="info__content-header-title">{'label'}</span>
<button onClick={console.log}>Close info</button>
</div>
<div className="info__content-header-text">{'select.HelpText'}</div>
</div>
</div>
);
}
Don't forget to remove your event listener, as soon as you don't need them anymore, e.g. when the component gets unmounted:
return () => document.removeEventListener('click', handleCLickOutside);
Otherwise, this could lead to bugs and performance issues.
Here is your stackblitz with the applied example.

React MUI Multiple Select with Checkboxes not Check marking Initial Selected Options

I am still new in React and MUI, so please spare my life. I am learning to build Multiple Select Options with Checkboxes and I have populated the Dropdown Options from an Array.
I also have set up the initial/default state options. The initial/default state options (Nintendo & XBoX) are showing up in the main Input Select Field but unfortunately not being checked marked in the Dropdown Checkboxes, such as in the screenshot below.
Screenshot 1
What I'm trying to achieve is to be like this (all the selected options (Nintendo & XBoX) from the default state is showing on the checkboxes at initial page load):
Screenshot 2
This is my Live Demo coding in https://stackblitz.com/edit/react-rahqrq?file=demo.js
I'm confused, I can tick all the other option checkboxes in the dropdown except the default Initial Options from the State. It's unclickable and unchangeable, I have no idea why. Any tips and clues are greatly appreciated, Thanks in advance, please pardon my poor Grammar and Best Regards.
import * as React from 'react';
import OutlinedInput from '#mui/material/OutlinedInput';
import InputLabel from '#mui/material/InputLabel';
import MenuItem from '#mui/material/MenuItem';
import FormControl from '#mui/material/FormControl';
import ListItemText from '#mui/material/ListItemText';
import Select from '#mui/material/Select';
import Checkbox from '#mui/material/Checkbox';
const ITEM_HEIGHT = 48;
const ITEM_PADDING_TOP = 8;
const MenuProps = {
PaperProps: {
style: {
maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
width: 250,
},
},
};
const variants = [
{
id: 3,
name: 'Voucher',
},
{
id: 1,
name: 'Top Up',
},
{
id: 2,
name: 'Game Key',
},
{
id: 12,
name: 'Other',
},
{
id: 11,
name: 'Nintendo',
},
{
id: 10,
name: 'Xbox',
},
];
export default function MultipleSelectCheckmarks() {
const [variantName, setVariantName] = React.useState([{
id: 11,
name: 'Nintendo',
},{
id: 10,
name: 'Xbox'
},]);
const handleChange = (event) => {
const {
target: { value },
} = event;
const preventDuplicate = value.filter((v, i, a) => a.findIndex((t) => t.id === v.id) === i);
setVariantName(
// On autofill we get a the stringified value.
typeof preventDuplicate === 'string' ? preventDuplicate.split(',') : preventDuplicate
);
};
return (
<div>
<FormControl sx={{ m: 1, width: 300 }}>
<InputLabel id="demo-multiple-checkbox-label">Tag</InputLabel>
<Select
labelId="demo-multiple-checkbox-label"
id="demo-multiple-checkbox"
multiple
value={variantName}
onChange={handleChange}
input={<OutlinedInput label="Tag" />}
renderValue={(selected) => selected.map((x) => x.name).join(', ')}
MenuProps={MenuProps}
>
{variants.map((variant) => (
<MenuItem key={variant.id} value={variant}>
<Checkbox checked={variantName.indexOf(variant) > -1} />
<ListItemText primary={variant.name} />
</MenuItem>
))}
</Select>
</FormControl>
</div>
);
}
You need to findIndex of each item in the initial list to make them checked.
Try like below
{
variants.map(variant => (
<MenuItem key={variant.id} value={variant}>
<Checkbox
checked={
variantName.findIndex(item => item.id === variant.id) >= 0
}
/>
<ListItemText primary={variant.name} />
</MenuItem>
));
}
Code sandbox => https://stackblitz.com/edit/react-rahqrq-sd2m2b?file=demo.js

How to sync two dropdown lists with different display in React?

I have a list of countries, with key, value, text.
I would like to have two Dropdown (https://react.semantic-ui.com/modules/dropdown/) list, one shows the key, the other the text.
The goal is to allow to choose by key of by text (we can type in the dropdown); if I update one, the other is synchronized immediately.
How can I achieve this ?
<Dropdown
id='form-input-country'
label='Country'
placeholder='Select Country'
fluid
search
selection
options={countryISOOptions} // will show text
/>
<Dropdown
id='form-input-country'
label='Country'
placeholder='Select Country'
fluid
search
selection
options={countryISOOptions} // want to show key + want to sync in both direction
/>
I import countryISOOptions which looks like:
export const countryISOOptions = [
{key: 'AF', value: '4', text: 'Afghanistan'},
{key: 'AL', value: '8', text: 'Albania'},
{key: 'DZ', value: '12', text: 'Algeria'},
...
Maintain 2 option arrays. One for text and other for keys(derived from the first options array). Then maintain just one state and an onChange for both dropdowns and you will be fine.
See working copy of your code.
See code snippet:
import React, { useState } from "react";
import { Dropdown } from "semantic-ui-react";
import "./styles.css";
const countryISOOptions = [
{ key: "AF", value: "4", text: "Afghanistan" },
{ key: "AL", value: "8", text: "Albania" },
{ key: "DZ", value: "12", text: "Algeria" }
];
const countryKeys = countryISOOptions.map(({ key, value }) => ({
value,
text: key
}));
export default function App() {
const [text, setText] = useState("");
const onChangeTextDropdown = (e, d) => {
console.log("onChangeTextDropdown", e.target.value);
console.log("d", d);
setText(d.value);
};
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<Dropdown
id="form-input-countryz"
label="Country"
placeholder="Select Country - text"
value={text}
onChange={onChangeTextDropdown}
fluid
search
selection
options={countryISOOptions} // will show text
/>
<Dropdown
id="form-input-country"
label="Country"
placeholder="Select Country - key"
value={text}
onChange={onChangeTextDropdown}
fluid
search
selection
options={countryKeys} // want to show key + want to sync in both direction
/>
</div>
);
}
If you are using controlled version, then each Dropdown is a typical Inputthat supports two props called value and onChange. I'll use hook in the following example,
const [value1, setValue1] = setState('')
const [value2, setValue2] = setState('')
const onValue1Change = e => {
const value = e.target.value
setValue1(value)
if (value === 'key') setValue2('country')
}
return (
<div>
<Dropdown
value={value1}
onChange={onValue1Change}
...
/>
<Dropdown
value={value2}
...
/>
</div>
)

Categories

Resources