react select show and hide menuList - reactjs

I am trying to use react-select. I have certain condition (boolean), when the param is true, some property/attribute of the react-select would change base on the logic. One if them is menuList. My objective, if the param is true, I want the menuList displayed and searchable, but when false, I want the menuList hidden but still available (not disabled, thats why I use onChange and onInputChange prop). Here is what I've set so far:
const isExist = true;
return (
<div style={{ width: '50%', margin: 20 }}>
<Select
id="asd"
value={selectedOption}
onChange={isExist ? this.handleChange : null}
onInputChange={isExist ? null : e => this.tests(e) }
options={options}
isClearable={true}
styles={style}
placeholder="Please type"
noOptionsMessage={() => isExist ? 'Zero Result' : null}
components={{ MenuList: () => isExist ? 'display the menu but how?' : null }}
/>
</div>
);
any help would be helpful. Thank you!

Based on the behaviour you describe you could use a controlled menuIsOpen props like this:
class App extends Component {
constructor(props) {
super(props);
this.state = {
isExist: false,
menuIsOpen: false,
selectedOption: null
};
}
onChange = e => {
this.setState({
selectedOption: e
});
};
onInputChange = (options, { action }) => {
if (this.state.isExist) {
if (action === "menu-close") {
this.setState({ menuIsOpen: false });
}
} else {
if (action === "input-change") {
// do whatever you want with the entered value
}
}
};
onFocus = e => {
if (this.state.isExist) this.setState({ menuIsOpen: true });
};
render() {
return (
<div style={{ width: "50%", margin: 20 }}>
<Select
id="asd"
value={this.state.selectedOption}
onFocus={this.onFocus}
onChange={this.onChange}
onInputChange={this.onInputChange}
options={options}
isClearable={true}
placeholder="Please type"
noOptionsMessage={() => (this.state.isExist ? "Zero Result" : null)}
menuIsOpen={this.state.menuIsOpen}
/>
</div>
);
}
}
Here a live example.

Related

Only check the clicked Switch, not working

I want to check only the clicked Switch, but if i click on a Switch, all Switches are toggle.
class LoadMeister extends Component {
constructor(props) {
super(props);
this.state = {
daten: [],
isLoading: true,
checked: false
};
this.userUpdate = this.userUpdate.bind(this);
}
userUpdate(checked) {
this.setState({ checked });
}
[...]
render() {
const ListeUser = this.state.daten.map(meister => (
<Col md={2} xs={3} sm={3} className="karten">
<h3
title={meister.SD_Vorname}
style={{
fontWeight: "bold",
textOverflow: "ellipsis",
overflow: "hidden",
whiteSpace: "nowrap"
}}
>
{meister.SD_Vorname} {meister.SD_Nachname}
</h3>
<hr />
<p />
<FaBeer size="2em" />
<FaBeer size="2em" style={{ float: "right" }} />
<div style={{ marginTop: "10px" }}>
<Switch
key={meister.SD_Emplid}
width={151}
onChange={this.userUpdate}
checked={this.state.checked}
offColor="#A5C9D7"
onColor="#00556A"
/>
</div>
</Col>
));
[Return()]
xxxxxxxxxxxxxxxxxxxxxxxxxx
So what i have to do, so that only the clicked Switch is Toggle ?
I use the react-switch Lib.
xxxxxxxxxxxxxxxxxxxxxxxxxx
Try something like:
userUpdate(checkedId) {
this.setState({ checkedId:id });
}
...rest of your code
<Switch
key={meister.SD_Emplid}
width={151}
onChange={()=>this.userUpdate(meister.SD_Emplid)}
checked={this.state.checkedId===meister.SD_Emplid}
offColor="#A5C9D7"
onColor="#00556A"
/>
Yes indeed, all switches are toggled because you have just one value in your state for all switches : this.state.checked.
You need to make one value for each switch, you can use an array or an object for this :
With an array
// constructor
this.state = {
checked: [] // all switches
};
// Update the checked state at the right index
userUpdate(checked, index) {
const newChecked = this.state.checked
newChecked[index] = checked
this.setState({ checked: newChecked });
}
// render
const ListeUser = this.state.daten.map((meister,index) => (
// More code
<Switch
key={meister.SD_Emplid}
width={151}
onChange={(checked) => this.userUpdate(checked, index)} // We need to know which index is clicked
checked={this.state.checked[index]}
offColor="#A5C9D7"
onColor="#00556A"
/>
)
With an object:
// constructor
this.state = {
checked: {} // all switches, keys will be the daten ids
};
// Update the checked state
userUpdate(checked, id) {
const newChecked = this.state.checked
newChecked[id] = checked
this.setState({ checked: newChecked });
}
// render
const ListeUser = this.state.daten.map(meister => (
// More code
<Switch
key={meister.SD_Emplid}
width={151}
onChange={(checked) => this.userUpdate(checked, meister.SD_Emplid)}
checked={this.state.checked[meister.SD_Emplid]}
offColor="#A5C9D7"
onColor="#00556A"
/>
)
The two solutions are very similar.
You use the checked state for all of your Switch components, which results in the behaviour you experience.
A solution would be to save checked in your meister objects:
userUpdate(index, checked) {
const { daten } = this.state;
daten[index].checked = checked;
this.setState({ daten });
}
/* inside your render */
this.state.daten.map((meister, index) => (
/* ... */
<Switch
{/* ... */}
checked={meister.checked}
onChange={this.userUpdate.bind(this, index)}
/>
));
I use the second argument of .bind to add index as the first argument to the userUpdate function.

How to get multiple checkbox values in reactjs using antd checkbox?

I am getting the checkboxes based on table columns. If I select check box I am getting only one checked value. I want to get multiple checked values.
How do I achieve that?
class StudentTable extends React.Component {
state = {
columns: [
{
title: "StudentID",
dataIndex: "studentid",
key: "studentid"
},
{
title: "Name",
dataIndex: "name",
key: "name"
}
]
};
onChange = e => {
alert(JSON.stringify(e));
console.log(`checked = ${e.target.checked}`);
console.log(`checked = ${e.target.name}`);
this.setState({
[e.target.name]: e.target.value
});
if (e.target.checked === true) {
let filterData = this.state.columns.filter(columnData => {
return e.target.name === columnData.dataIndex;
});
CsvData.push(filterData[0].dataIndex);
console.log("After Filter", filterData[0].dataIndex);
}
};
render() {
return (
<div>
{this.state.columns.map(columData => {
return (
<div key={columData.key}>
<Checkbox name={columData.dataIndex} onChange={this.onChange}>
{columData.dataIndex}
</Checkbox>
</div>
);
})}
<CSVLink data={CsvData}>Download me</CSVLink>;<h2>Student Data</h2>
<Table
dataSource={data}
columns={this.state.columns}
pagination={{ pageSize: 5 }}
rowKey={record => record.id}
onChange={this.handleChange}
/>
</div>
);
}
}
Use checkbox group instead of checkbox
<Checkbox.Group
options={this.state.columns.map(column => ({label:column.dataIndex, value: column.dataIndex}))}
onChange={this.onChange}
/>
I hope this would help
Antd's Checkbox Group https://ant.design/components/checkbox/#components-checkbox-demo-group

How to test components wrapped in an antd Form?

I've been struggling with this for months, now. Although there's a lot of speculation on the correct way to test antd wrapped components, none of the suggestions worked for this particular component.
So, I have a component which is a modal with an antd form. In this form, I have a few fields: an input, a select and a tree select, nothing too fancy.
It's basically this:
class FormModal extends React.Component {
static propTypes = {
data: propTypes.object,
form: propTypes.object,
scopes: propTypes.array.isRequired,
clients: propTypes.array.isRequired,
treeData: propTypes.array.isRequired,
isEditing: propTypes.bool.isRequired,
isSaving: propTypes.bool.isRequired,
onCancel: propTypes.func.isRequired,
onSave: propTypes.func.isRequired,
onFilterTreeData: propTypes.func.isRequired,
visible: propTypes.bool.isRequired
}
static defaultProps = {
data: null,
form: {}
}
state = {
selectedScopes: [],
newScopes: [],
inputVisible: false,
inputValue: ''
};
componentDidMount() {
// do stuff
}
handleSave = () => {
// do stuff
}
handleSelectedScopesChange = (event) => {
// do stuff
}
updateTreeSelect = () => {
const { form } = this.props;
const { selectedScopes } = this.state;
form.setFieldsValue({
allowedScopes: selectedScopes
});
}
handleRemoveTag = (removedTag) => {
const selectedScopes = this.state.selectedScopes.filter(scope => scope !== removedTag);
const newScopes = this.state.newScopes.filter(scope => scope !== removedTag);
this.setState({ selectedScopes, newScopes }, this.updateTreeSelect);
}
showInput = () => {
this.setState({ inputVisible: true }, () => this.input.focus());
}
handleInputChange = (e) => {
const inputValue = e.target.value;
this.setState({ inputValue });
}
handleInputConfirm = () => {
const { newScopes, inputValue } = this.state;
let tags = newScopes;
if (inputValue && tags.indexOf(inputValue) === -1) {
tags = [inputValue, ...tags];
}
this.setState({
newScopes: tags,
inputVisible: false,
inputValue: '',
});
}
saveInputRef = input => this.input = input
renderTags = (scopeArrays) => {
const tags = scopeArrays.map(scopeArray =>
scopeArray.map((permition) => {
let scopeType = null;
if (permition.includes('read') || permition.includes('get')) scopeType = 'blue';
if (permition.includes('create') || permition.includes('write') || permition.includes('send')) scopeType = 'green';
if (permition.includes('update')) scopeType = 'gold';
if (permition.includes('delete')) scopeType = 'red';
return (
<Tag
key={permition}
color={scopeType || 'purple'}
style={{ margin: '2px 4px 2px 0' }}
closable
afterClose={() => this.handleRemoveTag(permition)}
>
{permition}
</Tag>
);
})
);
return [].concat(...tags);
}
render() {
const {
selectedScopes,
newScopes,
inputValue,
inputVisible
} = this.state;
const {
form,
treeData,
clients,
isEditing,
isSaving,
onCancel,
onFilterTreeData,
visible
} = this.props;
const {
getFieldDecorator,
getFieldsError,
} = form;
const selectedScopesTags = this.renderTags([newScopes, selectedScopes]);
const clientOptions = clients.map(client => (<Option key={client._id}>{client.name}</Option>));
return (
<Modal
className="user-modal"
title={isEditing ? 'Editing Group' : 'Creating Group'}
visible={visible}
onCancel={onCancel}
footer={[
<Button key="cancel" onClick={onCancel}>Cancel</Button>,
<Button
key="save"
type="primary"
loading={isSaving}
onClick={this.handleSave}
disabled={formRules.hasErrors(getFieldsError())}
>
Save
</Button>
]}
>
<Form layout="vertical" onSubmit={this.handleSave}>
<Row gutter={24}>
<Col span={12}>
<FormItem label="Name">
{getFieldDecorator(
'name',
{ rules: [formRules.required, { max: 20, message: 'Group name can\'t excede 20 characters' }] }
)(
<Input />
)}
</FormItem>
</Col>
<Col span={12}>
<FormItem label="Client">
{getFieldDecorator(
'client', { rules: [formRules.required] }
)(
<Select placeholder="Please select client">
{clientOptions}
</Select>
)}
</FormItem>
</Col>
<Col span={24}>
<FormItem label="Scopes">
{getFieldDecorator(
'allowedScopes'
)(
<TreeSelect
treeData={treeData}
filterTreeNode={onFilterTreeData}
onChange={this.handleSelectedScopesChange}
treeCheckable
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
showCheckedStrategy="SHOW_PARENT"
searchPlaceholder="Filter by scopes"
className="groups__filter groups__filter--fill"
/>
)}
</FormItem>
</Col>
<Col span={24}>
<Card
title="Selected Scopes"
style={{ width: '100%' }}
>
<div>
{inputVisible && (
<Input
ref={this.saveInputRef}
type="text"
size="small"
style={{ width: 350 }}
value={inputValue}
onChange={this.handleInputChange}
onBlur={this.handleInputConfirm}
onPressEnter={this.handleInputConfirm}
/>
)}
{!inputVisible && (
<Tag
onClick={this.showInput}
style={{ background: '#fff', borderStyle: 'dashed', margin: '5px 0' }}
>
<Icon type="plus" /> New Scope
</Tag>
)}
</div>
{ selectedScopesTags.length > 0 ? (
selectedScopesTags
) : <p>No scopes selected yet.</p> }
</Card>
</Col>
</Row>
</Form>
</Modal>
);
}
}
export default Form.create()(FormModal);
I know that this component is urging for a refactoring, but that's not my job right now. I need to UI test this and validate if everything is working properly.
I'm trying to test if the form fields are rendering properly. I'm using Jest and Enzyme and so far I got this:
describe('Groups modal', () => {
let props;
const groupsModal = () =>
mount(
<FormModal.WrappedComponent {...props} />
)
beforeEach(() => {
props = {
data: null,
scopes: [],
clients: [],
treeData: [],
isEditing: false,
isSaving: false,
onCancel: jest.fn(),
onSave: jest.fn(),
onFilterTreeData: jest.fn(),
visible: true,
form: {
getFieldsError: jest.fn(() => { return {} }),
getFieldDecorator: () => (component) => component
}
};
});
it('should render properly', () => {
const wrapperDivChilds = groupsModal().find('.user-modal').children();
expect(wrapperDivChilds.length).toBeGreaterThanOrEqual(1);
});
describe('form fields', () => {
it('should render name input', () => {
const nameInput = groupsModal().find(Input);
expect(nameInput.length).toBe(1);
});
it('should render clients select', () => {
const clientsSelect = groupsModal().find(Select);
expect(clientsSelect.length).toBe(1);
});
it('should render scopes tree select', () => {
const scopesTreeSelect = groupsModal().find(TreeSelect);
expect(scopesTreeSelect.length).toBe(1);
});
});
});
All of my tests that validate if the inputs were rendered are failing.
As you can see, I tried mocking the form decorator functions, but still no success...
So, my question is: how should I test this component?
If you want to assert only initially rendered info, you can directly import your wrapped component and do:
const component = mount(<WrappedComponent {...props} form={formMock} />);
On the other hand if you don't want to use mock form or want to assert any logic connected with form-observer-wrappedComponent chain you can do next:
const wrapper = mount(<FormWrapperComponent {...props} />);
// props include anything needed for initial mapPropsToFields
const component = wrapper.find(WrappedComponent);
// Do anything with form itself
const { form } = component.instance().props;
Kinda more time consuming but worked perfectly for me, spent some time to understand how to avoid using form mock. In this case you don't need to mock anything connected with Form itself and if any field is not rendered as you expect you can be sure that problem is in another piece of code.

react-select prevent menu to open onInputChange

I'm trying to use the react-select component as an input and a select component.
Doing so I would like to prevent the menu to open while the user is typing the input.
I just can't find a way to update this behavior by either a prop or updating the onInputChange method.
My problem if I decide to use a controlled state with the menuIsOpen prop is that I can't manage to reopen the Menu control is clicked.
Here is what I have so far, do you guys any idea of how this could be achieved ?
<StyledSelect
components={{ IndicatorSeparator }}
{..._.omit(this.props, [])}
filterOption={() => true}
inputValue={inputValue}
onChange={value => {
this.select.setState({ menuIsOpen: false });
this.setState({ selectValue: value });
}}
onInputChange={(value, event) => {
if (event && event.action === 'input-change') {
this.setState({ inputValue: value });
}
}}
openMenuOnClick={false}
/>
Example
I think you are in the right direction using onInputChange and I would add a controlled menuIsOpen props like the following code:
class App extends Component {
constructor(props) {
super(props);
this.state = {
menuIsOpen: false
};
}
openMenu = () => {
this.setState({ menuIsOpen: !this.state.menuIsOpen });
};
onInputChange = (props, { action }) => {
if (action === "menu-close") this.setState({ menuIsOpen: false });
};
render() {
const DropdownIndicator = props => {
return (
components.DropdownIndicator && (
<div
onClick={this.openMenu}
style={{ display: "flex", alignItems: "center" }}
>
<span style={{ marginLeft: 12 }}>kg</span>
<components.DropdownIndicator
{...props}
onClick={() => {
console.log("ici");
}}
/>
</div>
)
);
};
return (
<Select
components={{ DropdownIndicator }}
options={options}
onChange={this.onChange}
onInputChange={this.onInputChange}
menuIsOpen={this.state.menuIsOpen}
/>
);
}
}
The idea with this code is to fire an onClick event on the DropdownIndicator custom component.
Here a live example.

How do I implement field validation for 'react-select'

I need a simple "required" validation for 'react-select' (github repo).
In the latest version it uses css-in-js approach. So I have custom styles:
export const customStyles = {
control: (base, state) => ({
...base,
}),
menu: (base, state) => ({
...base,
}),
menuList: (base, state) => ({
...base,
}),
}
How can I change e.g. borderColor if field is invalid?
On this point there's an issue opened on GitHub.
I see two different approaches:
the "lazy" one, where you change the border colour by adding a specific className. Example here.
As you want to custom the original select I would recommend to embed your customSelect in a separate file. Then you can pass a props isValid and use it to change your borderColor.
class CustomSelect extends React.Component {
render() {
const {
isValid
} = this.props
const customStyles = {
control: (base, state) => ({
...base,
// state.isFocused can display different borderColor if you need it
borderColor: state.isFocused ?
'#ddd' : isValid ?
'#ddd' : 'red',
// overwrittes hover style
'&:hover': {
borderColor: state.isFocused ?
'#ddd' : isValid ?
'#ddd' : 'red'
}
})
}
return <Select styles={ customStyles } {...this.props}/>
}
}
A help for someone who doesn't want to add all time some codes for only of this required validation in react-select. Just use react-hook-form-input.
<RHFInput
as={<Select options={options} />}
rules={{ required: 'Please select an option'}}
name="reactSelect"
register={register}
setValue={setValue}
/>
Where this RHFInput from react-hook-form-input was just a save for me.. Complete example- react-hook-form-input.
import React from 'react';
import useForm from 'react-hook-form';
import { RHFInput } from 'react-hook-form-input';
import Select from 'react-select';
const options = [
{ value: 'chocolate', label: 'Chocolate' },
{ value: 'strawberry', label: 'Strawberry' },
];
function App() {
const { handleSubmit, register, setValue, reset, errors } = useForm();
return (
<form onSubmit={handleSubmit(data => console.log(data))}>
<RHFInput
as={<Select options={options} />}
rules={{ required: 'Please select an option'}}
name="reactSelect"
register={register}
setValue={setValue}
/>
<span className="text-danger">
{errors.reactSelect && errors.reactSelect.type === 'required' && "Please select"}
</span>
<button type="button">Reset Form</button>
<button>submit</button>
</form>
);
}
Hope, it will help someone like me as a beginner in react.
render.js
export const renderSelect = (props) => (
<div>
<Select
{...props}
value={props.input.value}
onChange={(value) => props.input.onChange(value)}
onBlur={() => props.input.onBlur(props.input.value)}
options={props.options}
key={props.input.value}
/>
{props.meta.touched && (props.meta.error && <p style={{ color: "red",fontSize:"12px" }}>{props.meta.error}</p>)}
</div>
);
implementForm.js
<Field
name="sex"
component={renderSelect}
options={Status}
isClearable={true}
validate={required}
/>
requiredFileMessage.js
const required = value => value ? undefined : 'Required'
The best way I found is to create a transparent input field that will be queried via javascript standard checkValidity. This should have absolute positioning and 100% width & height. You can then bind a listener to the input field for the invalid event created by checkValidity
Here is the code I use. There are alterations for use of value field as well as some styling (for MDB inputs) , but you can just change the classes of input to match your own styles libraries. In this manner your validation styling will be the same as your existing form inputs.
Hopes this helps someone.
/**************************************************************************************
*** Select - New Select Control Using react-select (to stop stupid probs with MDB) ***
**************************************************************************************/
// Use This progressively instead of InputSelect
/* Differences from native ReactSelect
Validation - Applies transparent input overlay that works with form checkValidity()
value: This is the record.field value not an object {label: x, value: y} as for react-select
grouped: Explicitly set grouped options when set true
objValue: Works the same as react-select value object
*/
// Note: value property works differently do react-select , use ObjValue to work same as react-select
export const Select = (props) => {
let { id, options, cols, value, objValue, label, grouped, ...o } = props
id = id ? id : 'react-select'
const [selected, setSelected] = useState(value)
const [invalid, setInvalid] = useState(false)
//DEFAULTS
if (!grouped) grouped = false
//--------------------------
// VALIDATION EVENT HANDLERS
//--------------------------
useEffect(() => {
//ADD EVENT HANDLER FOR FOR VALIDATION
let ele = document.getElementById(`${id}_invalid}`)
ele.addEventListener('invalid', (e) => {
console.log('e is ', selected, e)
if (typeof selected === 'undefined' || selected !== null) setInvalid(true)
})
//ON COMPONENT EXIT - REMOVE EVENT HANDLER
return () => {
ele.removeEventListener('invalid', () => {
setInvalid(false)
})
}
// eslint-disable-next-line
}, [])
//Value property (Allows Single field assignent) - translates to object in for {label:x, value:y}
useEffect(() => {
let val
if (grouped) {
val = _.findInGroup(options, 'options', (rec) => rec.value === value)
} else {
val = options.find((rec) => rec.value === value)
}
//console.log('Selected==>', val)
setSelected(val)
// eslint-disable-next-line
}, [value, options])
//objValue Property (Emulate standard react-select value object)
useEffect(() => {
if (objValue) {
setSelected(objValue)
}
// eslint-disable-next-line
}, [objValue])
//STYLING SAME AS MDB INPUT COMPONENTS
const customStyles = {
valueContainer: (provided, state) => ({
...provided,
backgroundColor: 'aliceblue',
}),
dropdownIndicator: (provided, state) => ({
...provided,
backgroundColor: 'aliceblue',
}),
}
const handleChange = (opt, i) => {
setSelected(opt)
//Callback function (i is used for nested data in record)
if (props && props.onChange) props.onChange(opt, i)
}
return (
<Col cols={cols}>
{label && <label className='tp-label text-uppercase'>{label}</label>}
<div className='select-wrapper'>
<ReactSelect
styles={customStyles}
value={selected ? selected : ''}
options={options}
onChange={(val, i) => handleChange(val, i)}
isSearchable={true}
{...o}
/>
<input
id={`${id}_invalid}`}
name={`${id}_invalid}`}
value={selected ? selected : ''}
onChange={() => {}}
tabIndex={-1}
className={`form-control tp-input w-100 ${invalid ? '' : 'd-none'}`}
autoComplete='off'
//value={selected}
onFocus={() => {
setInvalid(false)
}}
style={{
position: 'absolute',
color: 'transparent',
backgroundColor: 'transparent',
top: 0,
left: 0,
width: '100%',
height: '100%',
zIndex: 0,
}}
required={true}
/>
</div>
</Col>
)
}
look at this link:https://codesandbox.io/s/react-hook-form-controller-079xx?file=/src/index.js
you have to use Controller
<Controller
control={control}
rules={{ required: true }}
name="citySelect"
getOptionLabel={(option) => option.name}
getOptionValue={(option) => option.id}
options={citiesOption}
value={city}
onChange={handleCity}
className="basic-single"
classNamePrefix="select"
isRtl
placeholder="استان مورد نظر"
as={Select}
/>
{errors.citySelect && <p>choose a city</p>}
React-Select style for Bootstrap 5 validation
class CustomSelect extends React.Component {
render() {
const { valid, invalid } = this.props;
let borderColor = '#ced4da';
let focusBorderColor = '#66afe9';
let focusBoxShadow = '0 0 0 .2rem rgba(0, 123, 255, 0.25)';
if (valid) {
borderColor = '#198754';
focusBorderColor = '#198754';
focusBoxShadow = '0 0 0 .2rem rgba(25, 135, 84, .25)';
} else if (invalid) {
borderColor = '#dc3545';
focusBorderColor = '#dc3545';
focusBoxShadow = '0 0 0 .2rem rgba(220, 53, 69, .25)';
}
const customStyles = {
valueContainer: (provided, state) => ({
...provided,
borderColor: state.selectProps.menuIsOpen ? focusBorderColor : borderColor,
boxShadow: state.selectProps.menuIsOpen ? focusBoxShadow : 'none',
}),
};
return (
<Select styles={customStyles} {...this.props} />
);
}
}

Resources