I'm having issues disabling certain options within a large list within a React Select element. I have around 6,500 options that get loaded into the select. At first I was having issues with the search functionality lagging but then I started using react-select-fast-filter-options which took care of that problem. Now the issue is that I need to disable certain options depending on the propType "picks". Here is the code:
import React, {Component} from 'react'
import PropTypes from 'prop-types';
import 'react-select/dist/react-select.css'
import 'react-virtualized/styles.css'
import 'react-virtualized-select/styles.css'
import Select from 'react-virtualized-select'
import createFilterOptions from 'react-select-fast-filter-options';
let options = [];
if(typeof stockSearchStocks !== 'undefined') {
//loads in all available options from backend by laying down a static js var
options = stockSearchStocks
}
const filterOptions = createFilterOptions({options});
class StockSearch extends Component {
static propTypes = {
exchanges: PropTypes.array.isRequired,
onSelectChange: PropTypes.func.isRequired,
searchDisabled: PropTypes.bool.isRequired,
picks: PropTypes.array.isRequired,
stock_edit_to_show: PropTypes.number
}
/**
* Component Bridge Function
* #param stock_id stocks id in the database
*/
stockSearchChange = (stock_id) => {
this.props.onSelectChange(stock_id);
}
//this is my current attempt to at least
//disable options on component mount but this doesn't seem to be working
componentWillMount = () => {
console.log('picks!: ' + JSON.stringify(this.props.picks));
let pickIDs = this.props.picks.map((p) => p.stock_id);
options = options.map((o) => {
// console.log(pickIDs.indexOf(o.value));
if(pickIDs.indexOf(o.value)) {
// console.log('here is the option: ' + JSON.stringify(o));
// console.log('here is the option: ' + o.disabled);
o.disabled = true;
}
})
}
/**
* handles selected option from the stock select
* #param selectedOption
*/
handleSelect = (selectedOption) => {
this.stockSearchChange(selectedOption.value);
}
render() {
return (
<div className="stock-search-container">
<Select
name="stock-search"
options={options}
placeholder="Type or select a stock here..."
onChange={this.handleSelect}
disabled={this.props.searchDisabled}
value={this.props.stock_edit_to_show}
filterOptions={filterOptions}
/>
</div>
)
}
}
export default StockSearch;
I have tried filtering through the picks props and changing that options variable to include disabled:true but this lags the application and I'm not sure if that will work now that I'm using react-select-fast-filter-options as it seems to be doing some sort of indexing. Is there a way to filter through the options var to find all instances of the picks prop and disable those options quickly?
isDisabled={this.props.disabled}
You are passing a wrong prop.. For v2, the prop is isDisabled.
https://github.com/JedWatson/react-select/issues/145
In react-select v2:
add to your array of options a property 'disabled': 'yes' (or any other pair to identify disabled options)
use isOptionDisabled props of react-select component to filter options based on 'disabled' property
Here's an example:
import Select from 'react-select';
const options = [
{label: "one", value: 1, disabled: true},
{label: "two", value: 2}
]
render() {
<Select id={'dropdown'}
options={options}
isOptionDisabled={(option) => option.disabled}>
</Select>
}
More information about react-select props is in the docs and here's an example they reference.
use the following to disable an option.
import Select from 'react-select';
render() {
const options = [
{label: "a", value: "a", isDisabled: true},
{label: "b", value: "b"}
];
return (
<Select
name="myselect"
options={options}
</Select>
)
}
People are making it a JavaScript issue. I say make it a CSS one and simplify.
Use this
style={{display: month === '2' ? 'none' : 'block'}}
Like so...
There are only 28 days in February
const ComponentName =() => {
const [month, setMonth] = useState("")
const [day, setDay] = useState("")
return (
<>
<select
onChange={(e) => {
const selectedMonth = e.target.value;
setMonth(selectedMonth)>
<option selected disabled>Month</option>
<option value= '1'>January</option>
<option value= '2'>February</option>
</select>
<select
onChange={(e) => {
const selectedDay = e.target.value;
setDay(selectedDay)>
<option selected disabled>Day</option>
<option value= '28'>28</option>
<option value= '29' style={{display: month === '2' ? 'none' : 'block'}}>29</option>
<option value= '30'>30</option>
</select>
</>
)
}
export default ComponentName;
Related
I have a set of select menus and I need to find out the values for all of them when I reset them using reset buttons.
The problem is this only works on change event for options and I can't make it work on reset buttons on the first click as it doesn't detect the change.
Code sample here:
https://stackblitz.com/edit/react-rmp8kf
import React from 'react';
import { useState } from 'react';
import uuid from 'react-uuid';
export default function Select() {
const [value, setValue] = useState({
select1: '',
select2: '',
});
const selectOptions = [
{
options: [
{
text: 'All',
value: '0',
},
{
text: 'Blue',
value: '1',
},
{
text: 'Yellow',
value: '2',
},
{
text: 'Green',
value: '3',
},
],
},
{
options: [
{
text: 'All',
value: '0',
},
{
text: 'Black',
value: '1',
},
{
text: 'White',
value: '2',
},
],
},
];
const handleOnChange = (e) => {
const valueSelected = e.target.value;
setValue({
...value,
[e.target.name]: valueSelected,
});
printAllSelectValues();
};
const resetAllSelections = (e) => {
e.preventDefault();
setValue({
select1: '',
select2: '',
});
printAllSelectValues();
};
const resetSelection = (e) => {
e.preventDefault();
setValue({
...value,
[e.target.dataset.selectName]: '',
});
printAllSelectValues();
};
const printAllSelectValues = () => {
const selectMenus = document.querySelectorAll('select');
selectMenus.forEach((select) =>
console.log(select.name + '=' + select.value)
);
};
return (
<form>
<div>
<label>
<select
name="select1"
value={value.select1}
onChange={handleOnChange}
>
{selectOptions[0].options.map((option) => (
<option value={option.value} key={uuid()}>
{option.text}
</option>
))}
</select>
</label>
<button data-select-name="select1" onClick={resetSelection}>
Reset
</button>
</div>
<div>
<label>
<select
name="select2"
value={value.select2}
onChange={handleOnChange}
>
{selectOptions[1].options.map((option) => (
<option value={option.value} key={uuid()}>
{option.text}
</option>
))}
</select>
</label>
<button data-select-name="select2" onClick={resetSelection}>
Reset
</button>
</div>
<button onClick={resetAllSelections}>Reset All</button>
</form>
);
}
The stackblitz sandbox you linked seems to do exactly what you need to. You are getting both values correctly when:
You select any value in any of the 2 select elements
You reset any of the select values individually by using the reset button on the side of each select element
You reset both values at once by using the "Reset All" button
What is the problem? Do you want to get the updated values ?
UPDATE
Okey. So react has the nature that every state update is async, meaning that you need to wait for a state update to happen to use its updated values. Now, you cannot use promises or async/await to do this because react is built intentionally this way and gives you the tools to do so. So you need to use the useEffect hook for this.
https://stackblitz.com/edit/react-bekzpz
Note: worth mentioning that you don't need to grab the data from the HTML elements but in case you want to manipulate DOM elements in react, you are not supposed to do it using the document but by using the useRef hook
Well, you know when there's a 128 GB phone but it doesn't have a blue color so the next options section colors disables the blue color button? and when a 256 GB has only one color, this is exactly it
// react
import React, {SyntheticEvent, useCallback, useMemo, useState} from 'react';
// third-party
import classNames from 'classnames';
import {UncontrolledTooltip} from 'reactstrap';
import {useFormContext} from 'react-hook-form';
// application
import {colorType} from '~/services/color';
import {IProduct, IProductOption, IProductOptionValueBase, IProductVariant} from '~/interfaces/product';
interface Props extends React.HTMLAttributes<HTMLElement> {
product: IProduct;
namespace?: string;
}
function ProductForm(props: Props) {
const {
product,
namespace,
className,
...rootProps
} = props;
const {options} = product; // IProductOption
const {register, getValues} = useFormContext();
const ns = useMemo(() => (namespace ? `${namespace}.` : ''), [namespace]);
const optionsTemplate = options.map((option, optionIdx) => (
<div key={optionIdx} className="product-form__row">
<div className="product-form__title">{option.name}</div>
<div className="product-form__control">
{option.type === 'default' && (
<div className="input-radio-label">
<div className="input-radio-label__list">
{option.values.map((value, valueIdx) => (
<label key={valueIdx} className="input-radio-label__item">
<input
type="radio"
name={`${ns}${option.slug}`}
className="input-radio-label__input"
value={value.slug}
ref={register({required: true})}
onChange={(evt) => handleOptionChange(evt, option, value)}
/>
<span className="input-radio-label__title">{value.name}</span>
</label>
))}
</div>
</div>
)}
{option.type === 'color' && (
<div className="input-radio-color">
<div className="input-radio-color__list">
{option.values.map((value, valueIdx) => (
<React.Fragment key={valueIdx}>
<label
className={classNames('input-radio-color__item', {
'input-radio-color__item--white': colorType(value.color) === 'white',
})}
id={`product-option-${optionIdx}-${valueIdx}`}
style={{color: value.color}}
title={value.name}
>
<input
type="radio"
name={`${ns}${option.slug}`}
value={value.slug}
ref={register({required: true})}
onChange={(evt) => handleOptionChange(evt, option, value)}
/>
<span/>
</label>
<UncontrolledTooltip
target={`product-option-${optionIdx}-${valueIdx}`}
fade={false}
delay={{show: 0, hide: 0}}
>
{value.name}
</UncontrolledTooltip>
</React.Fragment>
))}
</div>
</div>
)}
</div>
</div>
));
const rootClasses = classNames('product-form', className);
return (
<div className={rootClasses} {...rootProps}>
<div className="product-form__body">
{optionsTemplate}
</div>
</div>
);
}
export default ProductForm;
The product has .attributes (fully working) and .variants (I'm still implementing the full interface and .options working
NOTE: This was a prebuilt theme, it has attributes and options, I added variants (still doesn't have all needed attributes for variants tho).
export interface IProductAttributeValue {
name: string;
slug: string;
}
export interface IProductAttribute {
name: string;
slug: string;
featured: boolean;
values: IProductAttributeValue[];
}
export interface IProductVariant {
sku: string;
name: string;
price: number;
stock: IProductStock;
attributes: IProductAttribute[];
}
export type IProductOption = IProductOptionDefault | IProductOptionColor;
export interface IProductOptionValueBase {
name: string;
slug: string;
customFields?: ICustomFields;
}
export interface IProductOptionValueColor extends IProductOptionValueBase {
color: string;
}
export interface IProductOptionBase {
type: string;
name: string;
slug: string;
values: IProductOptionValueBase[];
customFields?: ICustomFields;
}
export interface IProductOptionDefault extends IProductOptionBase {
type: 'default';
}
export interface IProductOptionColor extends IProductOptionBase {
type: 'color';
values: IProductOptionValueColor[];
}
Well, the variant should have a storage of maybe 128GB, speaking about abstraction, it can have any x: y, I've made a simple graph for this
All the possible choices are options1.length * options2.length = 9, but perhaps the 2 can't exist with an A, the 3 can only exist with A, ...etc (random, depends on the phone)
When I say options (plural), it's equivalent to a row of the picture above. and an option is just one input from that row.
What I've thought (and apparently failed many times in implementing) is, maybe the onChange of each input can take (evt) => handleOptionChange(evt, option, value)
and then, when optionsN is selected, iterate all the options which are optionsN+1, so when 1 is selected from the first options, iterate all the rest, and see if there exists a variant, if not, disable it
here's the idea
1 is selected, iterate the rest, 1, A ? exists, 1, B? doesn't, 1,C ? doesn't, sounds good
I said to myself, we can get the index of the last selected option as , getValues returns an object (not a Map!) and I've got a workarond
const handleOptionChange = (evt: SyntheticEvent, option: any, value: any) => {
const currentOptionIdx = product.options.findIndex(o => o.slug == option.slug)
const optionsToFilterWith = product.options.slice(0, currentOptionIdx + 1);
const optionsValues = getValues();
};
// ??
Here comes another problem, what if a user selected 1, then selected B, then selected 2, the optionsToFilterWith will be an array of only 1 option, the first one.
There're utility hooks here, useVariant, useSetVariant to set the variant (because when the variant changes, I'll change the price and some other details .
EDIT 1: I've just got an idea, we may use product.stocks or variant.stock to handle this, I'll update this once any progress is made.
EDIT 2: seems like the order idea is the one making the problem I'm encountering?
EDIT 3: here's a helper method to get variants by options (from the form)
const getVariantByOptions = useCallback(() => {
const formValues = getValues();
// form values which are empty are "", filter them out
const currentOptions = Object.entries(formValues[namespace]).filter(o => o[1])
// current product variants
let variants = product.variants;
// for each selected option (loops -rows count- times)
currentOptions.forEach(currentOpt => {
// each option is on the form [attributeSlug, valueSlug]
const [optName, optValue] = currentOpt;
variants = variants.filter(variant => {
return variant.attributes.find(attr => {
if (attr.slug === optName) {
return !!attr.values.find(v => v.slug === optValue)
}
})
})
})
return variants.length === 1 ? variants[0] : null
}, [product])
options is the name of the input group.
and here's how the form getValues returns
{
storage: "256-gb",
test: "v1"
}
and if nothing is selected in an options field, here's how it returns
{
storage: "256-gb",
test: ""
}
I'll appreciate any help, Thanks in advance.
I created a dynamically changing form, using my own components and I am not sure how can I get the information out of it since the select tag itself exists only in the children components.
I googled and found that I need to use refs, though I am not sure how to do it either. Another option I found is to use
document.getElementsByClassName, but for some reasons, it's not an optimal way to do it, apparently.
the parent class where the form exists
import React, {Component} from 'react';
import ChoicePair from'./ChoicePair';
import ChoosingPane from './ChoosingPane';
class AppComponent extends React.Component {
state = {
numChildren: 3
}
render () {
const children = [];
this.subjects = [ {name: 'a', dep: 1}, {name: 'b', dep: 2}, {name: 'c', dep: 1}, {name: 'd', dep: 1}];
for (var i = 0; i < this.state.numChildren; i ++) {
children.push(<ChoicePair key={i} number={i} options = {this.subjects} subjects = {this.subjects}/>);
};
return (
<div>
<form onSubmit={this.handleSubmit}>
<ChoosingPane addChild={this.onAddChild}>
{children}
</ChoosingPane>
<button type = "submit"> Get my perfect schedule </button>
</form>
{ console.log(document.getElementById("kira") + "WOW")
}
</div>
);
}
onAddChild = () => {
this.setState({
numChildren: this.state.numChildren + 1
});
}
handleSubmit(event) {
event.preventDefault();
alert("hi!");
}
}
export default AppComponent;
The mid component
import React from 'react';
const ChoosingPane = props => (
<div>
<div id="children-pane">
{props.children}
</div>
<div className="card calculator">
<p><button onClick={props.addChild}>+</button></p>
</div>
</div>
);
export default ChoosingPane;
the leaf class where the select tag exists (1)
import React, {Component} from 'react';
class ChoicePair extends Component {
constructor(props){
super();
console.log("KKKKKKK");
this.state = {
depValue: '-1',
subValue: '-1',
}
this.handleDepChange = this.handleDepChange.bind(this);
this.handleSubChange = this.handleSubChange.bind(this);
this.getNames = this.getNames.bind(this);
}
handleDepChange(event) {
this.setState({depValue: event.target.value});
}
handleSubChange(event) {
this.setState({subValue: event.target.value});
}
getNames(collection)
{
console.log(collection);
if(collection)
return collection.map( (item, i) => {
return <option id="kira" key = {i} value={i} cat = {item}>{item.name}</option>
});
}
render() {
//create filtered options constant
const subjectList = this.props.subjects.filter((subjName) => subjName.dep === Number(this.state.depValue));
console.log(subjectList + "kill me");
return(
<div>
<select name = "Select department" value={this.state.depValue} onChange = {this.handleDepChange}>
<option value = '-1' disabled>Department</option>
{this.getNames(this.props.options)}
</select>
<select name = "Select subject" value={this.state.subValue} onChange = {this.handleSubChange}>
<option value = '-1' disabled>Subject</option>
{this.getNames(subjectList)}
</select>
</div>
)
}
}
export default ChoicePair;
the leaf class where the select tag exists (2)
Apparently, getElementsByClassNames works, but you can also use references on Dom nodes, or you can look into your architecture and use callback functions.
This is a good link if you are just starting to work with react and need some basic guidance with forms
I want to store select default value if user not touch it in ReactJs. How is that possible?
<select onChange={this.valSelected.bind(this)}>
{currencies.map(function(name, index){
return <option value={name}>{name}</option>;
})}
</select>
and
valSelected(event){
this.setState({
valSelected: event.target.value
});
}
You can just add a value property to the select element, set by your state.
<select value={this.state.valSelected} onChange={this.valSelected.bind(this)}>
{currencies.map(function(name, index){
return <option value={name}>{name}</option>;
})}
</select>
This is described here in the react docs: Doc Link
Then set a default state for the component, either in the constructor or with getInitialState: What is the difference between using constructor vs getInitialState in React / React Native?
Use defaultValue to select the default value.
const statusOptions = [
{ value: 1, label: 'Publish' },
{ value: 0, label: 'Unpublish' }
];
const [statusValue, setStatusValue] = useState('');
const handleStatusChange = e => {
setStatusValue(e.value);
}
return(
<>
<Select options={statusOptions} defaultValue={[{ value: published, label: published == 1 ? 'Publish' : 'Unpublish' }]} onChange={handleStatusChange} value={statusOptions.find(obj => obj.value === statusValue)} required />
</>
)
The example code in the react-bootstrap site shows the following. I need to drive the options using an array, but I'm having trouble finding examples that will compile.
<Input type="select" label="Multiple Select" multiple>
<option value="select">select (multiple)</option>
<option value="other">...</option>
</Input>
You can start with these two functions. The first will create your select options dynamically based on the props passed to the page. If they are mapped to the state then the select will recreate itself.
createSelectItems() {
let items = [];
for (let i = 0; i <= this.props.maxValue; i++) {
items.push(<option key={i} value={i}>{i}</option>);
//here I will be creating my options dynamically based on
//what props are currently passed to the parent component
}
return items;
}
onDropdownSelected(e) {
console.log("THE VAL", e.target.value);
//here you will see the current selected value of the select input
}
Then you will have this block of code inside render. You will pass a function reference to the onChange prop and everytime onChange is called the selected object will bind with that function automatically. And instead of manually writing your options you will just call the createSelectItems() function which will build and return your options based on some constraints (which can change).
<Input type="select" onChange={this.onDropdownSelected} label="Multiple Select" multiple>
{this.createSelectItems()}
</Input>
My working example
this.countryData = [
{ value: 'USA', name: 'USA' },
{ value: 'CANADA', name: 'CANADA' }
];
<select name="country" value={this.state.data.country}>
{this.countryData.map((e, key) => {
return <option key={key} value={e.value}>{e.name}</option>;
})}
</select>
bind dynamic drop using arrow function.
class BindDropDown extends React.Component {
constructor(props) {
super(props);
this.state = {
values: [
{ name: 'One', id: 1 },
{ name: 'Two', id: 2 },
{ name: 'Three', id: 3 },
{ name: 'four', id: 4 }
]
};
}
render() {
let optionTemplate = this.state.values.map(v => (
<option value={v.id}>{v.name}</option>
));
return (
<label>
Pick your favorite Number:
<select value={this.state.value} onChange={this.handleChange}>
{optionTemplate}
</select>
</label>
);
}
}
ReactDOM.render(
<BindDropDown />,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root">
<!-- This element's contents will be replaced with your component. -->
</div>
// on component load, load this list of values
// or we can get this details from api call also
const animalsList = [
{
id: 1,
value: 'Tiger'
}, {
id: 2,
value: 'Lion'
}, {
id: 3,
value: 'Dog'
}, {
id: 4,
value: 'Cat'
}
];
// generage select dropdown option list dynamically
function Options({ options }) {
return (
options.map(option =>
<option key={option.id} value={option.value}>
{option.value}
</option>)
);
}
<select
name="animal"
className="form-control">
<Options options={animalsList} />
</select>
Basically all you need to do, is to map array. This will return a list of <option> elements, which you can place inside form to render.
array.map((element, index) => <option key={index}>{element}</option>)
Complete function component, that renders <option>s from array saved in component's state. Multiple property let's you CTRL-click many elements to select. Remove it, if you want dropdown menu.
import React, { useState } from "react";
const ExampleComponent = () => {
const [options, setOptions] = useState(["option 1", "option 2", "option 3"]);
return (
<form>
<select multiple>
{ options.map((element, index) => <option key={index}>{element}</option>) }
</select>
<button>Add</button>
</form>
);
}
component with multiple select
Working example: https://codesandbox.io/s/blue-moon-rt6k6?file=/src/App.js
A 1 liner would be:
import * as YourTypes from 'Constants/YourTypes';
....
<Input ...>
{Object.keys(YourTypes).map((t,i) => <option key={i} value={t}>{t}</option>)}
</Input>
Assuming you store the list constants in a separate file (and you should, unless they're downloaded from a web service):
# YourTypes.js
export const MY_TYPE_1="My Type 1"
....
You need to add key for mapping otherwise it throws warning because each props should have a unique key. Code revised below:
let optionTemplate = this.state.values.map(
(v, index) => (<option key={index} value={v.id}>{v.name}</option>)
);
You can create dynamic select options by map()
Example code
return (
<select className="form-control"
value={this.state.value}
onChange={event => this.setState({selectedMsgTemplate: event.target.value})}>
{
templates.map(msgTemplate => {
return (
<option key={msgTemplate.id} value={msgTemplate.text}>
Select one...
</option>
)
})
}
</select>
)
</label>
);
I was able to do this using Typeahead. It looks bit lengthy for a simple scenario but I'm posting this as it will be helpful for someone.
First I have created a component so that it is reusable.
interface DynamicSelectProps {
readonly id: string
readonly options: any[]
readonly defaultValue: string | null
readonly disabled: boolean
onSelectItem(item: any): any
children?:React.ReactNode
}
export default function DynamicSelect({id, options, defaultValue, onSelectItem, disabled}: DynamicSelectProps) {
const [selection, setSelection] = useState<any[]>([]);
return <>
<Typeahead
labelKey={option => `${option.key}`}
id={id}
onChange={selected => {
setSelection(selected)
onSelectItem(selected)
}}
options={options}
defaultInputValue={defaultValue || ""}
placeholder="Search"
selected={selection}
disabled={disabled}
/>
</>
}
Callback function
function onSelection(selection: any) {
console.log(selection)
//handle selection
}
Usage
<div className="form-group">
<DynamicSelect
options={array.map(item => <option key={item} value={item}>{item}</option>)}
id="search-typeahead"
defaultValue={<default-value>}
disabled={false}
onSelectItem={onSelection}>
</DynamicSelect>
</div>