React Select - Multi Select custom way to display multiple options - reactjs

I am looking to customize the multiselect and the way we create the display of showing selected options.
Right now, with many options selected the select component takes up a prohibitive amount of space for certain UIs. See example:
I'd like to utilize the out of the box chip display for selected options within the input, but I only want to show only a few selected options (like 3/4 max) and then add a "badge" count for the number of selected options that aren't shown in the value container in the input. The options that are selected but are past the max number of chips allowed to show in the input should show as selected within the dropdown list, while the chips that do show's values should not show in our dropdown.
I've implemented part of this with using a custom ValueContainer to show only the first few chip selections, and then adding a count of additional/"overflow" selections. I'm unsure of how I can utilize the prop hideSelectedOptions to achieve this to show selected items in the list only when my max is met without showing all of them since this prop takes a boolean.
Here's what I have so far: https://codesandbox.io/s/custom-react-select-sjtib
import React, { Component } from "react";
import Select, { components } from "react-select";
import { colourOptions } from "./docs/data";
import "./example.css";
class CustomSelect extends Component {
state = {
values: []
};
handleChange = values => {
this.setState({ values });
};
render() {
const { values } = this.state;
return (
<div>
<Select
hideSelectedOptions={values.length < 3 ? true : false}
isMulti
options={colourOptions}
onChange={this.handleChange}
value={values}
components={{ ValueContainer }}
/>
</div>
);
}
}
export default CustomSelect;
const ValueContainer = ({ children, getValue, ...props }) => {
let maxToShow = 3;
var length = getValue().length;
let displayChips = React.Children.toArray(children).slice(0, maxToShow);
let shouldBadgeShow = length > maxToShow;
let displayLength = length - maxToShow;
return (
<components.ValueContainer {...props}>
{!props.selectProps.inputValue && displayChips}
<div className="root">
{shouldBadgeShow &&
`+ ${displayLength} item${length != 1 ? "s" : ""} selected`}
</div>
</components.ValueContainer>
);
};

I would personally keep hideSelectedOptions={false} and go for styles property usage (options property to be more exact) and setting display: 'none' for the ones which shouldn't be visible:
const styles = {
option: (base, value) => {
return (shouldBeShown(value) ? { ...base } : { display: 'none'});
}
};
shouldBeShown(value) is a custom function for checking if the particular option should be shown.
In order to get option data you can use value.data.
Then you can set styles={styles} in Select component:
<Select
hideSelectedOptions={false}
isMulti
styles={styles}
onChange={this.handleChange}
options={options}
value={values}
components={{ ValueContainer }}
/>

Related

Make the dropdown button always show a placeholder text

I would like to realize a textfield and a dropdown. When users select an option in the dropdown, the selection will be written to the textfield. And users can write whatever they want in the textfield; the dropdown list is supposed to give users some possible examples/ideas.
It's more or less like a combobox, but I find combobox is always one line (please correct me if I'm wrong). I would expect the textarea to be big and have several lines.
I have written the following code, one thing I would like to improve is that, after selecting an option, I would like the dropdown button to always show Examples of sentences rather than the selected option.
Does anyone know how to achieve this?
I'm open to other third-party components that could realize similar logics.
StackBlitz: https://stackblitz.com/edit/react-ts-yrhmfn?file=App.tsx,index.tsx
import {
FluentProvider,
webLightTheme,
} from '#fluentui/react-components';
import {
Dropdown,
Option
} from '#fluentui/react-components/unstable';
import { TextField } from '#fluentui/react';
import * as React from 'react';
export default class Grouped extends React.Component<{}, { value: any }> {
constructor(props) {
super(props);
this.state = { value: '' };
}
land = ['A long sentence', 'Another long sentence', 'Another another long sentence'];
render() {
return (
<div>
<FluentProvider
className="fluent-provider"
style={{ display: 'flex' }}
theme={webLightTheme}
>
<Dropdown
placeholder="Examples of sentences"
onOptionSelect={(e, data) => {
if (data.optionText !== undefined)
this.setState({ value: data.optionText });
}}
>
{this.land.map((option) => (
<Option key={option}>{option}</Option>
))}
</Dropdown>
</FluentProvider>
<br/>
<TextField
label="Write a sentence:"
value={this.state.value}
multiline
rows={3}
autoAdjustHeight
resizable={false}
/>
</div>
);
}
}
Well, I can solve this but it's a bit of a hack :) My professional suggestion is to consider a different third party library. I looked at the FluentUI and there doesn't seem to be a way to control the currently selected value.
If there were, you could simply set the currently selected value to undefined. Barring that, here's some code that achieve what you're asking for:
{this.state.visible && (
<Dropdown
placeholder="Select an animal"
onOptionSelect={(e, data) => {
if (data.optionText !== undefined) {
this.setState({ value: data.optionText, visible: false });
setTimeout(() => {
this.setState({ value: data.optionText, visible: true });
}, 0);
}
}}
>
{this.land.map((option) => (
<Option key={option}>{option}</Option>
))}
</Dropdown>
)}
What this code does is essentially "unload" the dropdown after a selection is made and then reload it one tick later, which resets its internal state. It's definitely a "hacky" solution, but it gets the job done.
The dropdown uses slots, so actually it's really easy to achieve. You can simply overwrite the props of the underlying button to display always the same text:
<Dropdown
button={{children: "Placeholder"}}
>
<Option>A</Option>
<Option>B</Option>
<Option>C</Option>
<Option>D</Option>
</Dropdown>

How to disable the Text field name is disappearing when we moved out the input filed box in react js

I have made autocomplete features using Downshift using react js. But the problem is when I am searching for something its input field value is disappearing when I click on the outside. Here is the sample code.
import logo from './logo.svg';
import './App.css';
import React, { useState } from "react";
import Highlighter from "react-highlight-words";
import Downshift from "downshift";
import axios from 'axios';
function App() {
const [names, setnames] = useState([{
const [searchTerm, setSearchTerm] = useState('')
const [movie, setmovie] = useState([])
fetchMovies = fetchMovies.bind(this);
inputOnChange = inputOnChange.bind(this);
function inputOnChange(event) {
if (!event.target.value) {
return;
}
fetchMovies(event.target.value);
}
function downshiftOnChange(selectedMovie) {
alert(`your favourite movie is ${selectedMovie.title}`);
}
function fetchMovies(movie) {
const moviesURL = `https://api.themoviedb.org/3/search/movie?api_key=1b5adf76a72a13bad99b8fc0c68cb085&query=${movie}`;
axios.get(moviesURL).then(response => {
setmovie(response.data.results);
// this.setState({ movies: response.data.results });
});
}
return (
<Downshift
onChange={downshiftOnChange}
itemToString={item => (item ? item.title : "")}
>
{({
selectedItem,
getInputProps,
getItemProps,
highlightedIndex,
isOpen,
inputValue,
getLabelProps
}) => (
<div>
<label
style={{ marginTop: "1rem", display: "block" }}
{...getLabelProps()}
>
Choose your favourite movie
</label>{" "}
<br />
<input
{...getInputProps({
placeholder: "Search movies",
onChange: inputOnChange
})}
/>
{isOpen ? (
<div className="downshift-dropdown">
{movie
.filter(
item =>
!inputValue ||
item.title
.toLowerCase()
.includes(inputValue.toLowerCase())
)
.slice(0, 10)
.map((item, index) => (
<div
className="dropdown-item"
{...getItemProps({ key: index, index, item })}
style={{
backgroundColor:
highlightedIndex === index ? "lightgray" : "white",
fontWeight: selectedItem === item ? "bold" : "normal"
}}
>
{item.title}
</div>
))}
</div>
) : null}
</div>
)}
</Downshift>
);
}
export default App;
This is the sample code I have written. Also, when I click shift+home, it is also not working.
Problem 1: when the user clicked the outside text field value whatever I searched this is disappearing.
Problem 2: shift + home is not working also.
Anyone has any idea how to solve this problem?
when the user clicked the outside text field value whatever I searched this is disappearing.
One way you could do it is to set the stateReducer on the Downshift component:
This function will be called each time downshift sets its internal state (or calls your onStateChange handler for control props). It allows you to modify the state change that will take place which can give you fine grain control over how the component interacts with user updates without having to use Control Props. It gives you the current state and the state that will be set, and you return the state that you want to set.
state: The full current state of downshift.
changes: These are the properties that are about to change. This also has a type property which you can learn more about in the stateChangeTypes section.
function stateReducer(state, changes) {
switch (changes.type) {
case Downshift.stateChangeTypes.mouseUp:
return {
...changes,
isOpen: true,
inputValue: state.inputValue,
};
default:
return changes;
}
}
This way if you click outside the text field the dropdown will stay open and the input value won't be reset.
For a list of all state change types see the documentation here
You might also be able to get something working using the onBlur prop on the input, but I didn't get that working.

React select multiple true- comma separated value display

Is it possible to get the display of selected values as comma separated.. instead of the box with cross sign
import Select from 'react-select'
<Select
name=''
styles={customStyles}
isClearable
isMulti
/>
You can create your own custom MultiValueContainer component. To display comma separated options, we can do something like this:
render() {
const components = {
MultiValueContainer: ({ selectProps, data }) => {
const values = selectProps.value;
if (values) {
return values[values.length - 1].label === data.label
? data.label
: data.label + ", ";
} else return "";
}
};
return (
<Select
value={this.state.value}
onChange={this.handleChange}
isMulti
name="colors"
options={colourOptions}
className="basic-multi-select"
classNamePrefix="select"
components={components}
/>
);
}
here is the code sandbox link to see above code in action.
According to the React-Select docs, you can change the styles of the individual selected items.
But, they render as boxes with x buttons on them so that the user can choose to de-select any of the selected items.
I would suggest playing with the React-Select styles like this:
<Select
styles={customStyles}
/>

Access inputValue in <Option /> component in react-select 2.*

I'm trying to highlight (underline or bold) the text that was entered within all my displayed options. This was quite straightforward in react-select 1.* but I fail in 2.*. when trying to access the inputValue within the custom component.
This code snippet illustrates my attempt when inputValue would be available as in props:
import React from 'react'
import match from 'autosuggest-highlight/match/index'
import parse from 'autosuggest-highlight/parse/index'
export default props => {
// Extract matching parts from the inputValue (value typed into text field)
const matches = match(props.label, props.inputValue)
const parts = parse(props.label, matches)
return (
<div>
{parts.map((part, index) => {
return !part.highlight ? (
<span>{part.text}</span>
) : (
<strong>{part.text}</strong>
)
})}
</div>
)
}
If you were using a custom Option component then you could access the inputValue from within the props.selectProps.
const Option = props => {
console.log('props', props);
const { innerProps, innerRef, selectProps, data } = props;
return (
<div ref={innerRef} {...innerProps}>
// generate your highlighted Option from data.label here, using
// selectProps.inputValue
</div>
);
};
// ...
<Select {...otherProps} components={{Option}} />

react-select How to hide dropdown menu programmatically when 'no results found' is shown?

Github Repo: react-select
After searching in the select box:
After typing a text that is not in the dropdown and enter is pressed. I want to hide the dropdown box.
My implementation:
<Select
ref={ input => this.input = input }
name="form-field-name"
searchable
autoBlur
clearable={false}
openOnFocus
onInputKeyDown={this.onInputKeyDown.bind(this)}
value={this.state.selectedOption}
onChange={this.handleChange.bind(this)}
options={this.props.items}
/>
using onInputKeyDown I am detecting enter keycode. What do I do to remove the dropdown there when 'No results found' is shown?
onInputKeyDown(e) {
if (e.keyCode === keys.ENTER) {
console.log('on input key down');
// How to detect 'No results found' shown?
// And then, how to close the dropdown?
}
}
In V2 you can achieve this by setting noOptionsMessage to a function that returns null:
<Select noOptionsMessage={() => null}/>
This will prevent the fallback option from displaying completely. Note that setting noOptionsMessage to null directly will result in an error, the expected prop type here is a function.
First method:
Turn off <Menu /> component to hide dropdown list.
<Select
components={{
...components,
Menu: () => null
}}
/>
Second method:
Turn off dropdown conditionally. e.g. When there is no value in input.
// Select.js
import { Menu } from './Menu'
<Select
{...props}
components={{
Menu
}}
/>
-------
// Menu.js
import { components } from 'react-select'
export const Menu = props => {
if (props.selectProps.inputValue.length === 0) return null
return (
<>
<components.Menu {...props} />
</>
)
}
Try using the noResultsText prop. Set it to null whenever you would want to hide it.
If you want to hide the menu when no more options are available you can try to override the MenuList component. This worked for me:
const MenuList = ({ children, ...props }: MenuListProps) => {
return Array.isArray(children) && children?.length > 0 ? (
<components.MenuList {...props}>
{children}
</components.MenuList>
) : null;
};

Resources