I am using react-select for my select dropdown. The issue I am having is that there is no empty option to reset the dropdown value if the user changes their mind.
Currently I am taking the options and manually adding an empty string, but I feel there must be something already in the library to handle this? I cannot find anything in the docs.
My code looks like the below, and there is a code sandbox here.
import React from "react";
import Select from "react-select";
const App = () => {
const options = [
{ value: "chocolate", label: "Chocolate" },
{ value: "strawberry", label: "Strawberry" },
{ value: "vanilla", label: "Vanilla" }
];
return <Dropdown options={options} />;
}
const Dropdown = ({ options }) => {
const optionsWithEmptyOption = [{ value: "", label: "" }, ...options];
return <Select options={optionsWithEmptyOption} />;
};
Plase check this out
https://codesandbox.io/s/zow1c?module=/example.js
import React, { Component } from 'react';
import CreatableSelect from 'react-select/creatable';
import { colourOptions } from './docs/data';
export default class CreatableSingle extends Component<*, State> {
handleChange = (newValue: any, actionMeta: any) => {
console.group('Value Changed');
console.log(newValue);
console.log(`action: ${actionMeta.action}`);
console.groupEnd();
};
handleInputChange = (inputValue: any, actionMeta: any) => {
console.group('Input Changed');
console.log(inputValue);
console.log(`action: ${actionMeta.action}`);
console.groupEnd();
};
render() {
return (
<CreatableSelect
isClearable
onChange={this.handleChange}
onInputChange={this.handleInputChange}
options={colourOptions}
/>
);
}
}
Empty Unicode
I add line to options, and write between the apostrophes empty unicode like this: ⠀⠀⠀⠀⠀⠀⠀⠀ .. you can mark it but dont see it.
const options = [
{ value: "", label: "⠀" },
{ value: "chocolate", label: "Chocolate" },
{ value: "strawberry", label: "Strawberry" },
{ value: "vanilla", label: "Vanilla" }
];
And I change this:
return <Select options={options} />;
what about const optionsWithEmptyOption = [{ value: null, label: "Select..." }, ...options];
I'm not really good with explanations, but #NicoHaase is right, so here it goes...
as far as I know, you must give a value to value null (if nothing) or string ... same for the label, #1 because of the user UX and second so react-select knows what to display. But if you really need to leave it in black, and you can try to modify in the styles, in order to have the same height as the other options.
Related
I'm writing a component that renders itself inside recursively and is data-driven
Attaching my sandbox snippet, as it will be easier to see there.
This is my data:
var builderStructureData = [
{
id: 123,
value: 3,
children: []
},
{
id: 345,
value: 5,
children: [
{
id: 4123,
value: 34,
children: [
{
id: 342342,
value: 33,
children: []
}
]
},
{
id: 340235,
value: 3431,
children: [
{
id: 342231342,
value: 3415,
children: []
}
]
}
]
}
];
and it renders like this:
This is my App.js:
import { useState } from "react";
import "./App.css";
import Group from "./components/group";
import builderStructureData from "./components/builderStructureData";
function App() {
const [builderStructure, setBuilderStructure] = useState(
builderStructureData
);
return (
<div className="App">
{builderStructure.map((x) => {
return <Group key={x.id} children={x.children} value={x.value} />;
})}
</div>
);
}
export default App;
And this is my recursive component:
import React from "react";
export default function Group(props) {
let childrenArray = [];
if (props.children) {
props.children.map((x) => childrenArray.push(x));
}
return (
<div className="group" draggable>
<p>this is value: </p>
<input value={props.value} readOnly={true}></input>
<button>Add Group</button>
{childrenArray.map((x) => {
return <Group key={x.id} children={x.children} value={x.value} />;
})}
</div>
);
}
I can render the components based on the data, and it seems to be handling recursion fine. I need to store the state on the App.js page and be able to change it from within child components. For example, if I update the "value" field of the component with ID = 342342, I want it to update that corresponding object in the state no matter how deeply nested it is, but not sure how to do that as it is not as simple as just passing a prop.
Am I taking the right approach with my code snippet? How can I do the state update?
I would advise the state normalization approach - here is an example for redux state - https://redux.js.org/usage/structuring-reducers/normalizing-state-shape - but you can use this approach with your state. So - your state will look like this:
state = {
items: {
[123]: {
id: 123,
value: 3,
childrenIds: []
},
[345]: {
id: 345,
value: 5,
childrenIds: [4123, 340235]
},
[4123]: {
id: 4123,
value: 34,
parentId: 345,
childrenIds: [342342]
},
[342342]: {
id: 342342,
value: 33,
parentId: 4123,
childrenIds: []
},
[340235]: {
id: 340235,
value: 3431,
parentId: 345,
childrenIds: [342231342]
},
[342231342]: {
id: 342231342,
value: 3415,
parentId: 340235
childrenIds: []
}
}
}
Here the field "childrenIds" is an optional denormalization for ease of use, if you want - you can do without this field. With this approach, there will be no problem updating the state.
You are thinking this in a wrong way, it should be very easy to do what you want.
The most imported thing is to make a little small changes in Group
Please have a look
import React from "react";
export default function Group(props) {
const [item, setItem] = React.useState(props.item);
let childrenArray = [];
if (item.children) {
item.children.map((x) => childrenArray.push(x));
}
const updateValue = ()=> {
// this will update the value of the current object
// no matter how deep its recrusive is and the update will also happen in APP.js
// now you should also use datacontext in app.js togather with state if you want to
// trigger somethings in app.js
item.value =props.item.value= 15254525;
setState({...item}) // update the state now
}
return (
<div className="group" draggable>
<p>this is value: </p>
<input value={item.value} readOnly={true}></input>
<button>Add Group</button>
{childrenArray.map((x) => {
return <Group item={x} />;
})}
</div>
);
}
The code above should make you understand how easy it is to think about this as an object instead of keys.
Hop this should make it easy for you to understand
I'm using this component here: https://react-select.com/home
What I'm trying to accomplish is on page load if my array contains a value in my Assigned element then I want to display that by default in my react-select box if not then I want my placeholder Assign to to show.
Here is how my select looks:
<Select
value={this.state.Product[0].Assigned ? this.state.Product[0].Assigned : selectedAssigned}
onChange={this.handleChangeAssigned}
options={this.state.AssignedList}
placeholder={<div>Assign to:</div>}
/>
In my Product[0].Assigned there is currently a value, however the dropdown still has the placeholder Assign To. I tried changing value to value={this.state.Product[0].Assigned} but still no luck.
Here is my change handle:
handleChangeAssigned = selectedAssigned => {
this.setState(
{ selectedAssigned },
() => console.log(`Option selected:`, this.state.selectedAssigned)
);
};
It seems like you are storing the same data in multiple places. You are checking if you have an assignment by looking at this.state.Product[0].Assigned. But when you select an assignment you are not updating that property. Instead you are updating a completely separate property this.state.selectedAssigned. this.state.Product[0].Assigned never changes so if you see a placeholder at first then you will always see a placeholder.
The value that you set on the Select needs to be the same as the value that you update.
import React from "react";
import Select from "react-select";
interface Option {
label: string;
value: string;
}
interface State {
Product: any[];
AssignedList: Option[];
selectedAssigned: Option | null;
}
export default class MyComponent extends React.Component<{}, State> {
state: State = {
Product: [],
AssignedList: [
{ label: "a", value: "a" },
{ label: "b", value: "b" },
{ label: "c", value: "c" }
],
selectedAssigned: null
};
handleChangeAssigned = (selectedAssigned: Option | null) => {
this.setState({ selectedAssigned }, () =>
console.log(`Option selected:`, this.state.selectedAssigned)
);
};
render() {
return (
<Select
value={this.state.selectedAssigned}
onChange={this.handleChangeAssigned}
options={this.state.AssignedList}
placeholder={<div>Assign to:</div>}
/>
);
}
}
Code Sandbox Link
So I have a little situation that I am stuck with. I have an array of holidays that looks like so:
export const holidays = {
federal: [
{ name: "New Year's Day", selected: false },
{ name: 'Martin Luther King, Jr. Day', selected: false },
{ name: "George Washington's Birthday", selected: false },
{ name: 'Memorial Day', selected: false },
{ name: 'Independence Day', selected: false },
{ name: 'Labor Day', selected: false },
{ name: 'Columbus Day', selected: false },
{ name: 'Veterans Day', selected: false },
{ name: 'Thanksgiving Day', selected: false },
{ name: 'Christmas Day', selected: false }
],
other: [
{ name: 'Black Friday', selected: false },
{ name: 'Christmas Eve', selected: false },
{ name: "New Year's Eve", selected: false }
]
};
My goal is to render 2 lists of holidays - one with a Federal Holidays label, and the other with a Other Holidays label. I need to be able to select all the checkboxes with an Add all options, and I also need to be able to select the check boxes individually.
Right now, only my Add all functionality is working. Here is my code:
// CheckboxList component
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import camelCase from 'lodash';
import Checkbox from 'common/components/Checkbox';
class CheckList extends Component {
static propTypes = {
data: PropTypes.array,
id: PropTypes.string.isRequired
};
...
get checkboxList() {
const { data } = this.props;
const { checked } = this.state;
return data.map(item => (
<Checkbox
id={camelCase(item)}
key={camelCase(item)}
checked={checked}
label={item}
/>
));
}
handleSelectAllCheckboxToggle() {
this.setState(({ checked }) => ({ checked: !checked }));
}
render() {
const { id } = this.props;
return (
<div className={this.baseClass}>
<Checkbox
id={id}
label="Add all"
onChange={this.handleSelectAllCheckboxToggle}
/>
{this.checkboxList}
</div>
);
}
}
The above is a reusable component that basically renders checkboxes with labels depending on how many items are inside the data prop (array) that is passed into the component. This reusable component is then passed into a parent component which is going to render the checkbox list:
// HolidaySchedule component
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import camelCase from 'lodash/camelCase';
import { holidays } from 'modules/utilities/constants';
import CheckboxList from './CheckboxList';
import Panel from 'common/components/Panel';
class HolidaySchedule extends Component {
get holidaysList() {
return Object.keys(holidays).map(holiday => (
<CheckboxList
key={camelCase(holiday)}
data={this.holidaysData(holiday)}
id={`${holiday}Holidays`}
/>
));
}
holidaysData(type) {
return holidays[type].map(holiday => holiday.name);
}
render() {
return (
<Panel>
{this.holidaysList}
</Panel>
);
}
}
This all ends up rendering a list of checkboxes with the holidays as their labels:
Sorry for the kinda long and in-depth post, I just wanted to make sure I didn't leave anything out. The functionality when clicking Add all for each group of checkboxes (Federal or Other) works fine.
However, I do not know what to do in order to also add functionality where I can select the checkboxes individually. I have only gotten as far as being able to select only one checkbox at a time, and it deselects the previous one selected. I would like to be able to Add all to select all checkboxes, click one of the checkboxes to uncheck it and deselect the Add all checkbox, and also just select one checkbox at a time. Stumped!
If anyone took the time to go through all this, thank thank you already!! If anyone has any advice or direction, then thank you a million times more!!!!
Give a man a fish, and you feed him for a day. Teach a man to fish, and you feed him for a lifetime
Are you looking for something like this ? I suppose this is the logic that you are looking for.
I've wrote a checkbox list demo with select all function for you. Its just logic, nothing looking fancy.
import React from "react";
import ReactDOM from "react-dom";
const App = () => {
const [holidays, setHolidays] = React.useState({
federal: [
{ name: "New Year's Day", selected: false },
{ name: "Martin Luther King, Jr. Day", selected: false },
{ name: "George Washington's Birthday", selected: false },
{ name: "Memorial Day", selected: false },
{ name: "Independence Day", selected: false },
{ name: "Labor Day", selected: false },
{ name: "Columbus Day", selected: false },
{ name: "Veterans Day", selected: false },
{ name: "Thanksgiving Day", selected: false },
{ name: "Christmas Day", selected: false }
],
other: [
{ name: "Black Friday", selected: false },
{ name: "Christmas Eve", selected: false },
{ name: "New Year's Eve", selected: false }
]
});
const handleOnChange = (e, type) => {
const { name, checked } = e.target;
const newHoliday = [...holidays[type]];
const index = newHoliday.findIndex(h => h.name === name);
if (index > -1) {
newHoliday[index] = { name, selected: checked };
}
setHolidays(h => ({ ...h, [type]: newHoliday }));
};
const handleOnSelectAll = (e, type) => {
const { checked } = e.target;
let newHoliday = [...holidays[type]];
if (!checked) {
newHoliday = newHoliday.map(opt => ({ ...opt, selected: false }));
} else {
newHoliday = newHoliday.map(opt => ({ ...opt, selected: true }));
}
setHolidays(h => ({ ...h, [type]: newHoliday }));
};
const renderCheckboxList = (options, type) =>
options.map(opt => (
<div>
<label>
<input
type="checkbox"
name={opt.name}
onChange={e => handleOnChange(e, type)}
checked={opt.selected}
/>
{opt.name}
</label>
</div>
));
const renderSelectAllCheckbox = type => (
<div>
<label>
<input
type="checkbox"
onChange={e => handleOnSelectAll(e, type)}
checked={holidays[type].every(opt => opt.selected)}
/>
{`Select All ${type}`}
</label>
</div>
);
return (
<section style={{ display: "flex", justifyContent: "space-around" }}>
<div>
<div>
<fieldset>
Federal Holidays
{renderSelectAllCheckbox("federal")}
{renderCheckboxList(holidays.federal, "federal")}
</fieldset>
</div>
<div>
<fieldset>
Other Holidays
{renderSelectAllCheckbox("other")}
{renderCheckboxList(holidays.other, "other")}
</fieldset>
</div>
</div>
<div>
State:
<pre>{JSON.stringify(holidays, null, 2)}</pre>
</div>
</section>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Some tips for you, if you want the final value to be in array form, you can use filter and map to get the name of the option.
Here is a working example: https://codesandbox.io/s/react-controlled-checkbox-list-f4t7g?fontsize=14&hidenavigation=1&theme=dark
Update (11/14/2019)
I've recreated another sandbox demo using React class. This time, I've created a component similar to yours (CheckboxList) that has name and onChange callback. Using these props allow you to update your parent holidays state.
You can find the working example here: https://codesandbox.io/s/react-class-controlled-checkbox-list-ejlfn?fontsize=14&hidenavigation=1&theme=dark
I think what you are missing is the onChange callback to update your parent state everytime there are changes made to the checkboxes. Your code are missing some important stuffs.
I am using react-select for autocomplete and option related field. When i select the option it passes whole that option object as {value: 'abc', label: 'ABC'} but i wanted only to pass the value as a string not the object. Thus, i used getOptionValue but it is not working as expected.
This is what I have done
<Field
name='status'
component={SearchableText}
placeholder="Search..."
options={status}
styles={styles}
getOptionLabel={option => option.label}
getOptionValue={option => option.value}
/>
I have used both getOptionLabel and getOptionValue but is still passing the selected option in object form instead of just the value as string.
Expected one
status: 'active'
Current behavior
status: { value: 'active', label: 'Active'}
I couldn't find getOptionValue in the docs for react-select, but you could always create an adapter around react-select. i.e. create your own Select component that uses react-select's Select component internally. After doing this it becomes possible to create your own getOptionValue. You can use this to make sure the value is a string.
import React from "react";
import Select from "react-select";
class MySelect extends React.Component {
getSelectValue() {
return this.props.options.find(
option => this.props.getOptionValue(option) === this.props.input.value
);
}
render() {
console.log("value", this.props.input.value);
return (
<Select
value={this.getSelectValue()}
onChange={option => {
this.props.input.onChange(this.props.getOptionValue(option));
}}
options={this.props.options}
/>
);
}
}
MySelect.defaultProps = {
getOptionValue: v => v
};
const MyForm = reduxForm({ form: "MyForm" })(
class extends React.PureComponent {
render() {
return (
<Field
name="myCoolSelect"
component={MySelect}
options={[
{ value: "chocolate", label: "Chocolate" },
{ value: "strawberry", label: "Strawberry" },
{ value: "vanilla", label: "Vanilla" }
]}
getOptionValue={option => option.value}
/>
);
}
}
);
The above is a basic example of how to get this working. You may want to pass other input or meta props to take advantage of other redux-form features. e.g. onBlur, onFocus, etc. You can see it in action here: https://codesandbox.io/s/6wykjnv32n
I'm having trouble figuring out how to set a dynamic dropdown component with multiple-value selections to each rendered element in a feature I'm working on. I think I'm really close but ultimately need a bit of guidance.
Here's the component:
import React, { Component } from 'react'
import { List, Dropdown, Label } from 'semantic-ui-react'
const directions = [
{key: "0.0", text: "0.0", value: "0.0"},
{key: "22.5", text: "22.5", value: "22.5"},
{key: "45.0", text: "45.0", value: "45.0"},
{key: "67.5", text: "67.5", value: "67.5"},
{key: "90.0", text: "90.0", value: "90.0"}
]
const channels = [
{ch: 65, callsign: "TEST1"},
{ch: 49, callsign: "TEST2"},
{ch: 29, callsign: "TEST3"}
]
export default class DirectionalSelection extends Component {
constructor(props) {
super(props)
this.state = {
channels,
directions,
currentValues: {}
}
}
handleDropdownChange = (e, index, { value }) => {
this.setState(({ currentValues }) => {
currentValues[index] = value
return currentValues
})
}
handleDirAddition = (e, index, { value }) => {
this.setState(({ directions }) => {
directions[index] = [{ text: value, value }, ...this.state.directions]
return directions
})
}
render() {
const { channels, currentValues, directions } = this.state
return (
<div>
<List>
{channels.map((el, index) => (
<List.Item key={index}>
<Label>{el.ch}</Label>
<Dropdown
search
multiple
selection
scrolling
allowAdditions
options={directions}
value={currentValues[index]}
placeholder='Choose directions'
onAddItem={this.handleDirAddition.bind(this, index)}
onChange={this.handleDropdownChange.bind(this, index)}
/>
</List.Item>
))}
</List>
</div>
)
}
}
Right now every time I select dropdown values on any channel, currentValues returns as [object Object]: ["22.5", "45.0"]. I want to set the ch key in channels as the key and the dropdown values array as the value and append them to currentValues.
I hope I've clarified the question enough to understand. Here is a link to Semantic-UI-React docs with the original component I'm using: https://react.semantic-ui.com/modules/dropdown#dropdown-example-multiple-allow-additions. Thanks for the help!
I figured it out! It was so simple, just had to switch the params in handleDropdownChange = (e, index, { value }) to handleDropdownChange = (index, e, { value }). It was setting the event function as the object key.