I'm using react-select library to add dropdown menu for my app, but "required" doesn't work (I read a discussion on GitHub for this library, creators haven't added that function yet), so for now how do I make select required? (I read here another post with the same problem, solution presented there didn't work for me). My code is:
import React, { useState } from "react";
import Select from "react-select";
export default function App() {
const [data, setData] = useState();
const options = [
{ value: "1", label: "1" },
{ value: "2", label: "2" },
{ value: "3", label: "3" },
{ value: "4", label: "4" }
];
const handleSubmit = (e) => {
console.log(data);
};
return (
<div className="App">
<form onSubmit={handleSubmit}>
<input required placeholder="name" />
<Select
options={options}
onChange={(e) => setData(e.value)}
value={options.filter(function (option) {
return option.value === data;
})}
label="Single select"
placeholder={"Select "}
menuPlacement="top"
required
/>
<button> Submit</button>
</form>
</div>
);
}
code sandbox
In my code I have a couple of regular inputs (where required works fine) but I want the Select also be required. Any suggestions are greatly appreciated.
I would think outside the box — in this case the Select component. What can you add that gets the same result? I took a quick stab at this for you:
const [isValid, setIsValid] = useState(false);
// This effect runs when 'data' changes
useEffect(() => {
// If there is data, the form is valid
setIsValid(data ? true : false);
}, [data]);
...
return (
<div>
<Select ... />
{!isValid && <p>You must choose a value</p>}
<button disabled={!isValid}>Submit</button>
</div>
)
https://codesandbox.io/s/vigilant-wiles-6lx5f
In this example, we check the state of the Select component — if it's empty (the user hasn't chosen anything) the form is invalid and the submit button is disabled.
Related
I have a React component that renders an array with an input field and a radio button. The input field is working fine but when clicking the radio button even though the state gets updated, the value is not reflected in the UI.
Following is the implementation of the React component
import React, { useState } from 'react';
import './style.css';
export default function App() {
const [subTemplate, setSubTemplate] = useState([
{ name: 'main', outputFormat: 'html' },
{ name: 'something else', outputFormat: 'text' }
]);
const handleInputChange = (value, row, key) => {
const updatedSubTemplate = subTemplate.map(item => {
if (item.name === row.name) {
return {
...item,
[key]: value
};
}
return item;
});
console.log('updatedSubTemplate', updatedSubTemplate); // updatedSubTemplate consists of the modified value
setSubTemplate(updatedSubTemplate);
};
const renderSubTemplates = () => {
return subTemplate.map((item, index) => {
return (
<div key={index}>
<input
type="text"
value={item.name}
onChange={e => {
handleInputChange(e.target.value, item, 'name');
}}
/>
<div>
<input
type="radio"
name="html"
checked={item.outputFormat === 'html'}
onChange={e => {
handleInputChange(e.target.name, item, 'outputFormat');
}}
/>
HTML
</div>
<div>
<input
type="radio"
name="text"
checked={item.outputFormat === 'text'}
onChange={e => {
handleInputChange(e.target.name, item, 'outputFormat');
}}
/>
TEXT
</div>
</div>
);
});
};
return <div>{renderSubTemplates()}</div>;
}
Following is a stackblitz link for the above implementation : https://stackblitz.com/edit/react-ugat24
Note: The radio button works as expected if there's only 1 element in the array.
It is because you have different value in name attribute.
Radio buttons are used to select one option from a list of options. so they need to have the same name to group them .
<input
type="radio"
name={`group-${index}`}
checked={item.outputFormat === 'html'}
onChange={e => {
handleInputChange('html', item, 'outputFormat');
}}
/>
Working Sample
https://stackblitz.com/edit/react-4xxpev
There are 2 issues in your code.
You are passing the same name attribute for all the radio inputs.
You are passing the wrong value for the radio elements.
Use template literals to append the index value to each set of radio group elements in the array.
Here's working code
References
Radio Input
Template literals
I am working with React and React Forms and i am having trouble trying to handle the state changes for radio buttons. I have a function getField which dynamically renders the type of input from the state. I have another function HandleFormStateChange which handles the change event for the inputs but i am running into an issue with the radio buttons. The user should be able to select only one option at a time but it seems to be selecting simultaneous options.
Please check out this CodeSandbox.
This is the complete code:
import React from "react";
class App extends React.Component {
state = {
Forms: [{ name: "Radio", type: "radio", options: ["a", "b", "c"] }]
};
handleFormStateChange = (event, idx) => {
const target = event.target;
const form = [...this.state.Forms];
form[idx].value = "";
form[idx].value = target.type === "radio" ? target.value : form[idx].value;
this.setState({
form
});
};
getField = (field, index) => {
switch (field.type) {
case "radio":
return (
<div>
{field.options.map(option => (
<label key={field.type + "op" + option}>
{option}:
<input
onChange={event => {
this.handleFormStateChange(event, index);
}}
key={option}
type={field.type}
name={option}
value={option}
/>
</label>
))}
</div>
);
default:
return <div>Unknown form field</div>;
}
};
renderForm = () => {
return this.state.Forms.map((field, index) => (
<label key={index}>
{field.name}
{this.getField(field, index)}
</label>
));
};
render() {
return <div>{this.renderForm()}</div>;
}
}
export default App;
Any help will be appreciated. Thank you :)
It is selecting multiple options because your radio buttons are not grouped by their name, they have separate names of their own. I've made an edit in your codesandbox - we have to have same name for your radio buttons to group them together. The same is the case with checkboxes if you want to group them together.
I've made couple of changes to your code:
I've added a name to identify the radio-group.
state = {
Forms: [
{
name: "Radio",
radioGroupName: "chosenAlphabet",
type: "radio",
options: ["a", "b", "c"]
}
]
};
I've changed the name attribute in your field rendering function.
<input
onChange={event => {
this.handleFormStateChange(event, index);
}}
key={option}
type={field.type}
name={field.radioGroupName}
value={option}
/>
I have a list of countries, with key, value, text.
I would like to have two Dropdown (https://react.semantic-ui.com/modules/dropdown/) list, one shows the key, the other the text.
The goal is to allow to choose by key of by text (we can type in the dropdown); if I update one, the other is synchronized immediately.
How can I achieve this ?
<Dropdown
id='form-input-country'
label='Country'
placeholder='Select Country'
fluid
search
selection
options={countryISOOptions} // will show text
/>
<Dropdown
id='form-input-country'
label='Country'
placeholder='Select Country'
fluid
search
selection
options={countryISOOptions} // want to show key + want to sync in both direction
/>
I import countryISOOptions which looks like:
export const countryISOOptions = [
{key: 'AF', value: '4', text: 'Afghanistan'},
{key: 'AL', value: '8', text: 'Albania'},
{key: 'DZ', value: '12', text: 'Algeria'},
...
Maintain 2 option arrays. One for text and other for keys(derived from the first options array). Then maintain just one state and an onChange for both dropdowns and you will be fine.
See working copy of your code.
See code snippet:
import React, { useState } from "react";
import { Dropdown } from "semantic-ui-react";
import "./styles.css";
const countryISOOptions = [
{ key: "AF", value: "4", text: "Afghanistan" },
{ key: "AL", value: "8", text: "Albania" },
{ key: "DZ", value: "12", text: "Algeria" }
];
const countryKeys = countryISOOptions.map(({ key, value }) => ({
value,
text: key
}));
export default function App() {
const [text, setText] = useState("");
const onChangeTextDropdown = (e, d) => {
console.log("onChangeTextDropdown", e.target.value);
console.log("d", d);
setText(d.value);
};
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<Dropdown
id="form-input-countryz"
label="Country"
placeholder="Select Country - text"
value={text}
onChange={onChangeTextDropdown}
fluid
search
selection
options={countryISOOptions} // will show text
/>
<Dropdown
id="form-input-country"
label="Country"
placeholder="Select Country - key"
value={text}
onChange={onChangeTextDropdown}
fluid
search
selection
options={countryKeys} // want to show key + want to sync in both direction
/>
</div>
);
}
If you are using controlled version, then each Dropdown is a typical Inputthat supports two props called value and onChange. I'll use hook in the following example,
const [value1, setValue1] = setState('')
const [value2, setValue2] = setState('')
const onValue1Change = e => {
const value = e.target.value
setValue1(value)
if (value === 'key') setValue2('country')
}
return (
<div>
<Dropdown
value={value1}
onChange={onValue1Change}
...
/>
<Dropdown
value={value2}
...
/>
</div>
)
I am using creatable select to allow the user to input a new option that is not in the dropdown list. But after looking I cannot seem to see a way of setting the maximum input to 50 characters for input before creating in the creatable select.
I have looked at taking the new entry and if over 50 characters deleting it however this seems a long way round and would like to use something that is shorter on the actual input when the user selects create.
import CreatableSelect from 'react-select/creatable';
const cars = [
{ label: "audi", value: 1 },
{ label: "bmw", value: 2 },
{ label: "ford", value: 3 },
{ label: "VW", value: 4 },
];
const selectOption = () => (
<div className="app">
<div className="container">
<CreatableSelect
options={cars}
placeholder={"check and enter new car name here"}
isClearable
onChange={(opt, meta) => console.log(opt, meta)}
/>
</div>
</div>
);
export default selectOption
I am hoping there is a max input option i just dont know. Thank you for taking the time to look at this/help, very much appreciated.
Simply put you can use the <CreatableSelect/> s onCreateOption prop. You will have to maintain your options and value in component state. Then put your control logic inside handleCreateOption function ( for onCreateOption prop ). Your handleCreateOption will be something like this. ( look at codesandbox for full code )
handleCreateOption = inputValues => {
if (inputValue.length < 50) {
const newOption = createOption(inputValue);
this.setState({
options: [...options, newOption],
value: newOption
});
}
}
codesandbox : https://codesandbox.io/embed/react-codesandboxer-example-cjbgu
If you don't want the user to be able to type more than the limit, you can add maxLength attribute to the input.
import React, { useEffect, useRef } from 'react';
const myRef = useRef(null);
useEffect(() => {
myRef.current.inputRef.style.minWidth = 'max-content';
myRef.current.inputRef.placeholder = placeholderText;
myRef.current.inputRef.maxLength = 20;
}, []);
<CreatableSelect
ref={myRef}
{...props}
options={options}
value={value}
name={name}
onChange={handleChange}
placeholder={''}
classNamePrefix="multi-select-custom"
isMulti={isMulti}
/>
i have a react-select component which i define like this:
<Select
id="portf"
options={opts}
onChange={value => portfolioSelector(value)}
placeholder="Select Portfolio"
/>
with opts = [{label: any, value:1}, {label:Two, value:2}].
The values when selected are stored in the state via portfolioSelector function. The problem is that when i select a value it wasn't show in the select field. My main component is this:
const PortfolioSelector = ({
opts,
portfolioSelector
}) => {
if (opts) {
return (
<div className="portfolio select-box">
<label htmlFor="selectBox" className="select-box__label">
Portfolio
</label>
<div className="select-box__container">
<Select
id="portf"
options={opts}
onChange={value => portfolioSelector(value)}
placeholder="Select Portfolio"
/>
</div>
<div className="separator" />
</div>
);
}
return (
<div>Loading</div>
);
};
Do you know why?
This is an alternative solution that i used.
Demo: https://codesandbox.io/s/github/mkaya95/React-Select_Set_Value_Example
import React, { useState } from "react";
import Select from "react-select";
export default function App() {
const [selectedOption, setSelectedOption] = useState("none");
const options = [
{ value: "none", label: "Empty" },
{ value: "left", label: "Open Left" },
{ value: "right", label: "Open Right" },
{
value: "tilt,left",
label: "Tilf and Open Left"
},
{
value: "tilt,right",
label: "Tilf and Open Right"
}
];
const handleTypeSelect = e => {
setSelectedOption(e.value);
};
return (
<div>
<Select
options={options}
onChange={handleTypeSelect}
value={options.filter(function(option) {
return option.value === selectedOption;
})}
label="Single select"
/>
</div>
);
}
<Select
options={this.props.locale}
onChange={this.selectCountryCode}
value={{label : this.state.selectedCountryLabel}}
/>
The value property expects the shape Array.
The value is handled really bad, and it needs hacks like here, explained here.
Long story short; the value works differently. You'd expect
value={MY_VALUE}, but it works instead
value={{label: MY_VALUE}}.
First thing is you are created the wrong array, if label: any or Two is string you have to add double quote.
Look at this:
opts = [{label: "any", value:1}, {label:"Two", value:2}]
Second, You must remember the options in this case is opts is an array of object which have label and value, what the data you want to add to your state?
<Select
id="portf"
options={opts}
onChange={value => portfolioSelector(value.value /* or if you want to add label*/ value.label)}
placeholder="Select Portfolio"
/>
You can simply put the value property as Selected.label
value={selectedOption.label}
I fixed it.
You forgot add value property. Use this, check the working code:
const opts = [{ label: 'any', value: 1 }, { label: 'Two', value: 2 }];
const PortfolioSelector = ({ options }) => {
if (options) {
return (
<div className="portfolio select-box">
<label htmlFor="selectBox" className="select-box__label">
Portfolio
</label>
<div className="select-box__container">
<Select
id="portf"
options={options}
value={this.state.opts1}
onChange={value => this.setState({ opts1: value })}
placeholder="Select Portfolio" />
</div>
<div className="separator" />
</div>
);
}
return <div>Loading</div>;
};
and call your component
<PortfolioSelector options={opts} />
For any one who is facing problems with React-Select getting populated or React-select doesn't get selected value; try to give it a dynamic key attribute who changes every time your data changes (either options array or selected option), so it will re-render with new data.
this is my solution
Demo: https://codesandbox.io/s/prod-rgb-o5svh?file=/src/App.js
code:
import axios from 'axios'
import {useEffect, useState} from 'react'
import Select from 'react-select'
import "./styles.css";
export default function App() {
const [users, setUsers] = useState()
const [userId, setUserId] = useState()
useEffect(()=>{
const getUsers = async () => {
const {data} = await axios.get('https://jsonplaceholder.typicode.com/users')
setUsers(data)
}
getUsers()
},[])
const options = users && users.map(user =>{
return {label: user.name, value: user.id}
})
console.log(userId)
return (
<div className="App">
{users && (
<Select
placeholder='Select user...'
isSearchable
value={options.label}
options={options}
onChange={(option) => setUserId(option.value) }
/>
)}
</div>
)
}
Please provide lable and value object together like {label: ?, value: ?}
const selectedValue = options.find(x => x.value===value);
<Select
id="portf"
options={opts}
onChange={value => portfolioSelector(value)}
placeholder="Select Portfolio"
value={label : selectedValue.label, value: (value ? selectedValue.value : "")}
/>
opts = [{label: any, value:1}, {label:Two, value:2}]
value must be string.