How show Highlighted options in react-select - reactjs

I am using react-select. But I don't know how to get the value of the currently highlighted option from the list options.
E.g. if a user pressed the key down or up button, I want to know which option is selected.
I haven't found any usable props in the documentation.
Not looking solutions like below.
Get value of highlighted option in React-Select

Sadly the library doesn't provide such a feature. However it applies the [class-prefix]__option--is-focused to the option that is focused. You can then easily get the value you want by checking classes change in pure Javascript.
This answer implement the class ClassWatcher that enable you to check class addition or removal on a specific node like:
new ClassWatcher(targetNode, 'your-class', onClassAdd, onClassRemoval)
So you could add this watcher to each options of the select by using querySelectorAll on the ref of the select. First step is to initialised the component with a few options and some states like isMenuOpen, focusedValue and the selectedOption:
const OPTIONS = [
{ value: "chocolate", label: "Chocolate" },
{ value: "strawberry", label: "Strawberry" },
{ value: "vanilla", label: "Vanilla" }
];
export default function App() {
const [isMenuOpen, setIsMenuOpen] = React.useState(false);
const [focusedValue, setFocusedValue] = React.useState("");
const [selectedOption, setSelectedOption] = React.useState(null);
const ref = React.useRef(null);
return (
<div className="App">
<p>Focused value: {focusedValue}</p>
<Select
ref={ref}
classNamePrefix="my-select"
value={selectedOption}
onChange={setSelectedOption}
options={OPTIONS}
isMenuOpen={isMenuOpen}
onMenuOpen={() => setIsMenuOpen(true)}
onMenuClose={() => {
setFocusedValue("");
setIsMenuOpen(false);
}}
/>
</div>
);
}
Now we can use ClassWatcher to update the focusedValue state when the class my-select__option--is-focused change. This as to be done when the ref is not null and when the menu is open so we can use a useEffect hook for that:
React.useEffect(() => {
if (ref && isMenuOpen) {
const menu = ref.current.select.menuListRef;
const options = menu.querySelectorAll(".my-select__option");
// add class watcher to each options
options.forEach((option, index) => {
new ClassWatcher(
option,
"my-select__option--is-focused",
() => setFocusedValue(OPTIONS[index].value),
() => {}
);
});
}
}, [ref, isMenuOpen]);
You can check here the complete example:

The Option component has an isFocused prop you that could be used. I'm looking at injecting a ref into the custom option prop and whenever that option is focus, update the ref to the value of that option.
import React from "react";
import Select, { components, OptionProps } from "react-select";
import { ColourOption, colourOptions } from "./docs/data";
export default () => {
const focusdRef = React.useRef(colourOptions[4]);
const Option = (props: OptionProps<ColourOption>) => {
const { isFocused, data } = props;
if (isFocused) focusdRef.current = data;
return <components.Option {...props} />;
};
return (
<Select
closeMenuOnSelect={false}
components={{ Option }}
styles={{
option: (base) => ({
...base,
border: `1px dotted ${colourOptions[2].color}`,
height: "100%"
})
}}
defaultValue={colourOptions[4]}
options={colourOptions}
onKeyDown={(e) => {
if (e.key === "ArrowRight") {
console.log(focusdRef.current);
}
}}
/>
);
};
So here whenever you press the right arrow, you have access to the current focused value.
code sandbox:

Related

Removing items in react-select with MultiValueContainer

I am using react-select to implement a multi-value drop down but using our internal UI component library to render the selected values in the input box. I am overriding the MultiValueContiner with our component. It renders fine, I can select items and they are added and rendered in the input box. The problem is with removing items. What can I access from the onClick handler of the component to remove it from the currently selected options? Do I simply need to add the currentValue & setCurrentValue state accessors to each menu option items and access through e.g. props.data.setCurrentValue()?
Custom MultiValueContainer
import { useState } from 'react';
import Select, { InputActionMeta, components, MultiValueGenericProps, MultiValue, ActionMeta } from 'react-select';
// import component from internal UI lib, dummy import here
import MyUIObject from './MyUIObject';
interface MenuOption {
value: string;
label: string;
}
export interface Props {
title: string;
items: MenuOption[];
}
const MyUIObjectValueContainer = (props: MultiValueGenericProps<MenuOption>) => {
return (
<components.MultiValueContainer {...props}>
<MyUIObject
text={props.data.label}
onClick={ (e) => {
e.stopPropagation();
e.preventDefault();
// HOW TO REMOVE FROM SELECTED OPTIONS ???
}}
/>
</components.MultiValueContainer>
);
};
function MyCustomMultiSelect(props: Props) {
const [inputValue, setInputValue] = useState('');
const [currentValue, setCurrentValue] = useState<MenuOption[]>([]);
function handleInputChange(newValue: string, actionMeta: InputActionMeta) {
if (actionMeta.action === 'input-change') {
setInputValue(newValue);
}
}
// clear manually typed search string from input
function handleOnBlur() {
setInputValue('');
}
function handleOnChange(newValue: MultiValue<MenuOption>, actionMeta: ActionMeta<MenuOption>) {
setCurrentValue( newValue as MenuOption[] );
}
return (
<Select
isMulti
isClearable
isSearchable
options={props.items}
closeMenuOnSelect={false}
onInputChange={handleInputChange}
inputValue={inputValue}
onBlur={handleOnBlur}
components={{ MultiValueContainer: MyUiObjectValueContainer }}
value={currentValue}
onChange={handleOnChange}
/>
);
}
export default MyCustomMultiSelect;
You haven't shared the code for your custom option component, so I'm assuming you built it correctly and made sure react-select's props are being spread into the custom component react-select docs.
In the case of multi select, the state you manage should be an array of selected options containing a label and an id properties. When you click on a selected option to remove it, react-select returns a new array of selected values with the option you clicked on filtered out. You should be able to just grab that returned array and set your selected option state I'm demoing in this simplified code:
import { useState } from "react";
import Select from "react-select";
export const options = [
{ label: "Option 1", value: 1 },
{ label: "Option 2", value: 2 },
{ label: "Option 3", value: 3 },
{ label: "Option 4", value: 4 },
{ label: "Option 5", value: 5 }
];
const App = () => {
const [value, setValue] = useState([]);
const handleChange = (e) => setValue(e);
return (
<Select
value={value}
options={options}
isMulti
onChange={handleChange}
closeMenuOnSelect={false}
/>
);
};
export default App;
You can have a look in this sandbox as well to see it working

React-select not displaying input when modifying value prop using state hooks

Ive been trying to make a dynamic input field(more input options appear on user input) with react-select, the input gets displayed when I'm not modifying value prop using state variables, but when I modify value prop using state hooks it is not displaying anything.
Here is the code snippet for without hooks which displays output just fine
import React,{useState} from "react"
import CreatableSelect from 'react-select/creatable';
export default function DynamicInput(){
const [val,setVal] = useState([])
const options = [
{ value: 'chocolate', label: 'Chocolate' },
{ value: 'strawberry', label: 'Strawberry' },
{ value: 'vanilla', label: 'Vanilla' }
]
const handleAdd=()=>{
const tempVal = [...val,[]]
setVal(tempVal)
}
const handleDel=(indx)=>{
const deleteVal = [...val]
deleteVal.splice(indx,1);
setVal(deleteVal);
}
return (
<div>
<button onClick={()=>handleAdd()}>Add</button>
{val.map((data,indx)=>{
return(
<div key = {indx}>
<CreatableSelect isClearable options={options} placeholder="Placeholder" } />
<button onClick = {()=>handleDel(indx)}>X</button>
</div>
)
})
}
</div>
);
}
Now I tried to use hooks to handle the input hooks.
import React, { useState } from "react";
import CreatableSelect from "react-select/creatable";
export function DynamicInputwHooks() {
const [val, setVal] = useState([]);
const [selectedOp, setSelectedOp] = useState([]);
const options = [
{ value: "chocolate", label: "Chocolate" },
{ value: "strawberry", label: "Strawberry" },
{ value: "vanilla", label: "Vanilla" }
];
const handleAdd = () => {
const tempVal = [...val, []];
const tempSel = [...selectedOp, []];
setSelectedOp(tempSel);
setVal(tempVal);
};
const handleSelection = (v, indx) => {
const tempSel = [...selectedOp];
tempSel[indx] = v.value;
setSelectedOp(tempSel);
};
const handleDel = (indx) => {
const deleteVal = [...val];
const deletesel = [...selectedOp];
deleteVal.splice(indx, 1);
deletesel.splice(indx, 1);
setVal(deleteVal);
setSelectedOp(deletesel);
};
return (
<div>
<button onClick={() => handleAdd()}>Add</button>
{val.map((data, indx) => {
return (
<div key={indx}>
<CreatableSelect
isClearable
options={options}
placeholder={"Placeholder"}
value={selectedOp[indx]}
onChange={(e) => handleSelection(e, indx)}
/>
<button onClick={() => handleDel(indx)}>X</button>
</div>
);
})}
</div>
);
}
I also added an input box with the same value and it displays value accordingly.
(I am unable to embed code using sandbox but this is the link : https://codesandbox.io/s/frosty-darwin-s3ztee?file=/src/App.js)
Upon using inspect element I found that when not using handling value there is an additional div as compared to when modifying value.
When not handling value prop
The single value div
when handling value prop
No single value div gets created
I cannot use useRef as there can be multiple inputs.
any help how to solve this would be appreciated thanks.
Ok so after some more searching I found this sandbox example that helps solve the problem
https://codesandbox.io/s/react-select-set-value-example-d21pt?file=/src/App.js
while this works for react-select but using it for react-select creatable requires appending to the options based on isNew prop of input when creating a new option.

React Button Multi-Select, strange style behaviour

I am trying to create a simple button multi-select in React but I'm currently getting unexpected behaviour. I'd like users to be able to toggle multiple buttons and have them colourise accordingly, however the buttons seem to act a bit randomly.
I have the following class
export default function App() {
const [value, setValue] = useState([]);
const [listButtons, setListButtons] = useState([]);
const BUTTONS = [
{ id: 123, title: 'button1' },
{ id: 456, title: 'button2' },
{ id: 789, title: 'button3' },
];
const handleButton = (button) => {
if (value.includes(button)) {
setValue(value.filter((el) => el !== button));
} else {
let tmp = value;
tmp.push(button);
setValue(tmp);
}
console.log(value);
};
const buttonList = () => {
setListButtons(
BUTTONS.map((bt) => (
<button
key={bt.id}
onClick={() => handleButton(bt.id)}
className={value.includes(bt.id) ? 'buttonPressed' : 'button'}
>
{bt.title}
</button>
))
);
};
useEffect(() => {
buttonList();
}, [value]);
return (
<div>
<h1>Hello StackBlitz!</h1>
<div>{listButtons}</div>
</div>
);
}
If you select all 3 buttons then select 1 more button the css will change.
I am trying to use these as buttons as toggle switches.
I have an example running #
Stackblitz
Any help is much appreciated.
Thanks
I think that what you want to achieve is way simpler:
You just need to store the current ID of the selected button.
Never store an array of JSX elements inside a state. It is not how react works. Decouple, only store the info. React component is always a consequence of a pattern / data, never a source.
You only need to store the necessary information, aka the button id.
Information that doesn't belong to the state of the component should be moved outside. In this case, BUTTONS shouldn't be inside your <App>.
Working code:
import React, { useState } from 'react';
import './style.css';
const BUTTONS = [
{ id: 123, title: 'button1', selected: false },
{ id: 456, title: 'button2', selected: false },
{ id: 789, title: 'button3', selected: false },
];
export default function App() {
const [buttons, setButtons] = useState(BUTTONS);
const handleButton = (buttonId) => {
const newButtons = buttons.map((btn) => {
if (btn.id !== buttonId) return btn;
btn.selected = !btn.selected;
return btn;
});
setButtons(newButtons);
};
return (
<div>
<h1>Hello StackBlitz!</h1>
<div>
{buttons.map((bt) => (
<button
key={bt.id}
onClick={() => handleButton(bt.id)}
className={bt.selected ? 'buttonPressed' : 'button'}
>
{bt.title}
</button>
))}
</div>
</div>
);
}
I hope it helps.
Edit: the BUTTONS array was modified to add a selected property. Now several buttons can be selected at the same time.

Handle controlled checbox

I have a form where I am rendering checkbox from map, how can I handle when someone unchecked box? Now i am using isChecked to set it.
import React, {ChangeEvent, Fragment, useCallback, useEffect, useState} from 'react';
import Button from '#atlaskit/button/standard-button';
import {Checkbox} from '#atlaskit/checkbox';
import {Grid, GridColumn} from '#atlaskit/page';
import Form, {CheckboxField, Field, FormFooter} from '#atlaskit/form';
import {ValueType as Value} from "#atlaskit/select/types";
import Select from "#atlaskit/select";
import {sentinelVulnerabilities} from "../constants";
import {invoke} from "#forge/bridge";
interface Option {
label: string;
value: string;
}
BasicConfiguration.defaultProps = {
jiraIssuePriorities: [],
}
const columns = 12;
export default function BasicConfiguration({jiraIssuePriorities, initPriorites, allowedVulnerabilities}: any) {
const [allowedVul, setAllowedVul] = useState<any | null>(undefined);
useEffect(() => {
(async () => {
await invoke("getStorage", {name: 'vulnerabilities_allowed'}).then(setAllowedVul);
})();
}, [])
const jiraIssuePrioritiesOptions = jiraIssuePriorities.map(({name, id}: any) => ({
label: name,
value: id,
}));
const shouldBySelected = (prioritySentinel: string) => {
if (initPriorites === undefined || Object.keys(prioritySentinel).length === 0.)
return '';
return initPriorites[prioritySentinel];
}
const shouldBeChecked = (vulnName: string): boolean => {
if (allowedVul === undefined || Object.keys(allowedVul).length === 0.) {
return false;
}
return allowedVul.includes(vulnName);
}
const onSubmit = async (data: any) => {
//Store mapping
await invoke("setStorage", {name: "vulnerabilities_allowed", data: data.vulnerabilities});
let priorities = {
note: undefined,
critical: undefined,
high: undefined,
medium: undefined,
low: undefined
};
if (data.hasOwnProperty('critical')) {
priorities.critical = data.critical.label;
}
if (data.hasOwnProperty('high')) {
priorities.high = data.high.label;
}
if (data.hasOwnProperty('medium')) {
priorities.medium = data.medium.label;
}
if (data.hasOwnProperty('low')) {
priorities.low = data.low.label;
}
if (data.hasOwnProperty('note')) {
priorities.note = data.note.label;
}
await invoke("setStorage", {name: 'vuln_priorities', data: priorities});
}
return (
<div style={{
display: 'flex',
width: '600px',
margin: '0 auto',
flexDirection: 'column',
paddingTop: 50,
}}>
<h3>Map Sentinel Vulnerabilities and Jira Issues</h3>
<Form onSubmit={onSubmit}>
{({formProps}) => (
<form {...formProps}>
{
sentinelVulnerabilities.map((element) => {
const isChecked = shouldBeChecked(element.value);
return <div>
<Grid spacing="compact" columns={columns}>
<GridColumn medium={4} css={{paddingTop: '5px'}}>
<CheckboxField name="vulnerabilities" value={element.value}>
{({fieldProps}) => <Checkbox {...fieldProps} label={element.label} isChecked={isChecked}
/>}
</CheckboxField>
</GridColumn>
<GridColumn medium={8}>
<Field<Value<Option>>
name={element.value}
isRequired={true}
defaultValue={{
value: shouldBySelected(element.value).toLowerCase(),
label: shouldBySelected(element.value)
}}
>
</Field>
</GridColumn>
</Grid>
</div>
})
}
</div>
);
}
What i want to achive is when page render have checkbox checked based on function shouldBeChecked() but I want that user can uncheck the box and submit the form. For now user is not able to unchecked the box, checkbox is always checked.
isChecked should be in a state, so it's value can be changed between different renders, otherwise it's value will always be the same returend from const isChecked = shouldBeChecked(element.value); on the first render within the map function.
And it's better to evaluate isChecked outside map function, because every time the component renders, the shouldBeChecked function will be running again which assins value to isChecked. So it'd be better to put this const isChecked = shouldBeChecked(element.value); in useEffect with empty dependency.

reactjs-select + react-google-places-autocomplete: how to initialize with a place

I am using a ready made component from https://www.npmjs.com/package/react-google-places-autocomplete for google autocomplete places
But I want to initialize it with place. (because when i edit a form, i have to show the place there)
import React from "react";
import GooglePlacesAutocomplete from "react-google-places-autocomplete";
const GooglePlacesAutocompleteComponent = () => (
<div>
<GooglePlacesAutocomplete
apiKey="xxxxxxxxxxxxxxx"
/>
</div>
);
export default Component;
The above is the component
and use is as
<GooglePlacesAutocompleteComponent />}
I know react-google-places-autocomplete uses react-select AsyncSelect
<AsyncSelect
{...selectProps}
loadOptions={fetchSuggestions}
getOptionValue={({ value }) => value.place_id}
/>
the fetchsugestions is list of {label and value}
HOw to pass the intial value
This is best achieved following the docs of React-Select as suggested by the creator. But to achieve what you want to do, you'll need React State.
import { useState, useEffect } from "react"
import GooglePlacesAutocomplete from "react-google-places-autocomplete"
const Places = () => {
const [data, setData] = useState("");
//our default data
useEffect(() => {
data === "" ? setData("") : setData(data.label);
}, [data]);
// updating our default data
return (
<GooglePlacesAutocomplete
apiKey={process.env.REACT_APP_MAP_API_KEY}
autocompletionRequest={{
componentRestrictions: {
country: ["ng"] //to set the specific country
}
}}
selectProps={{
defaultInputValue: data, //set default value
onChange: setData, //save the value gotten from google
placeholder: "Start Destination",
styles: {
input: (provided) => ({
...provided,
color: "#222222"
}),
option: (provided) => ({
...provided,
color: "#222222"
}),
singleValue: (provided) => ({
...provided,
color: "#222222"
})
}
}}
onLoadFailed={(error) => {
console.log(error);
}}
/>
)
}
export default Places;
We use useEffect to update the defualt value we set on condition that we are getting an actual value. If we're not getting actual value, we're not saving it.

Resources