how to handle displayed text in a select? - javascript

I need to display only part of the selected option:
const refertiItems = this.state.referti.map((referti, i) => {
return (
<option key={referti.hash_referto} >
[ {referti.proprietario} - {referti.tipo_esame} - {referti.data_esame.split('T')[0]} ] {referti.hash_referto}
</option>
)
});
and then
Label for="type" text="Seleziona un referto (proprietario - tipo esame - data)" />
<select
name="careGiver"
placeholder="Selezionare Referto"
onKeyPress={this.onEnter}
value={this.codiceReferto}
onChange={this.handleInputChange}
>
<option default value="vuoto" />
{refertiItems}
</select>
When i select an option, i want to be displayed only {referti.proprietario} and {referti.tipo_esame}, but not {referti.hash_referto}.
I can't just take it off from the option.
Any advice?

You most definitely can update the text. You can do this by updating your state array when an item is selected to present the appropriate display text. This is just a rough example, but the concept of how it could be done is shown:
const {
useState,
useEffect,
useRef
} = React;
function App() {
const [opts, setOpts] = useState([{
label: 'one',
active: 'one',
inactive: 'one remove_me'
},
{
label: 'two remove_me',
active: 'two',
inactive: 'two remove_me'
},
{
label: 'three remove_me',
active: 'three',
inactive: 'three remove_me'
},
]);
return (<div>
<select onChange={({target: {value}})=>{
// There are better ways to deep copy, should look into replacing this
let deepCopy = JSON.parse(JSON.stringify(opts)).map(e=>{
e.label = e.active==value?e.active:e.inactive;
return e;
});
setOpts(deepCopy);
}}>
{opts.map(o=>
<option key={o.active} value={o.active}>{o.label}</option>)}
</select>
</div>);
}
const el = document.querySelector("#app");
ReactDOM.render( < App / > , el);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.1/umd/react-dom.production.min.js"></script>
<div id="app"></div>

Related

Get value of select menus when clicking reset button React

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

How to achieve search with multiple input with react.js?

I am building a booking Hotel application and I am trying to achieve search by multiple inputs with react.js. I tried with the code below but I have two errors :
first error: that whenever I change the value of type room or the number of guests I get the result with the previous state, I always get the result with the previous state.the search is working fine but it using the previous state.
second error: How to achieve intersection of the two results of objects.
Or if anyone can come with a better solution.
import React, { useState } from 'react';
import './SearchPage.css';
import RoomList from './RoomList';
import { v4 as uuidv4 } from 'uuid';
const SearchPage = () => {
const Rooms = [
{
id: uuidv4(),
description: 'Chnambre du luxe 1',
size: 250,
guests: '4',
roomType: 'family room',
pets: false,
picture: 'pictures/pic.jfif',
price: 1000,
},
{
id: uuidv4(),
description: 'chambre du lux2',
picture: 'pictures/pic2.jfif',
price: 2000,
size: 300,
guests: '4',
roomType: 'single room',
pets: true,
},
{
id: uuidv4(),
description: 'chambre du luxe 3',
picture: 'pictures/pic2.jfif',
price: 2500,
size: 350,
guests: '2',
roomType: 'family room',
pets: true,
},
];
const [foundRooms, setFoundRooms] = useState(Rooms);
const [roomType, setRoomType] = useState('All');
const [guestNumber, setGuestNumber] = useState('0');
const HandleGuestNumber = (state) => {
const results = Rooms.filter((room) => room.guests === state);
return results;
};
const HandleRoomType = (state) => {
let results = [];
results = Rooms.filter((room) => {
let rooms = room.roomType.match(state);
return rooms;
});
return results;
};
const HandleOnChange = (event) => {
switch (event.target.name) {
case 'roomType':
setRoomType(event.target.value);
break;
case 'guestNumber':
setGuestNumber(event.target.value);
break;
default:
console.log(`Sorry, we are out of.`);
}
let foundRooms = HandleRoomType(roomType) && HandleGuestNumber(guestNumber);
setFoundRooms(foundRooms);
};
return (
<div>
<div className="SearchPage">
<h1> Search</h1>
</div>
<div className="SearchContainers">
<div className="SearchItem">
<span>Room Type : </span>
<select onChange={HandleOnChange} name="roomType">
<option value="All">All</option>
<option value="family room">Family Room</option>
<option value="single room">Single Room</option>
<option value="Luxiourious Family Room">
Luxiourious Family Room
</option>
<option value="Luxiourious Single Room">
Luxiourious Single Room
</option>
</select>
</div>
<div className="SearchItem">
<span> Guests : </span>
<select onChange={HandleOnChange} name="guestNumber">
<option value="0">0</option>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
</select>
</div>
</div>
);
};
export default SearchPage;
In your HandleOnChange function, you are doing this:
let foundRooms = HandleRoomType(roomType) && HandleGuestNumber(guestNumber);
setFoundRooms(foundRooms);
That is almost certainly not doing what you think it is doing.
Since HandleRoomType always returns an array, foundRooms always evaluates to HandleGuestNumber(guestNumber).
It sounds like you want foundRooms to be the rooms that are included in the arrays returned by both HandleRoomType and HandleGuestNumber.
One way you could do that is by doing another filter.
const roomsMatchingType = HandleRoomType(roomType)
const roomsMatchingGuestNumber = HandleGuestNumber(guestNumber)
const intersection = Rooms.filter(room => roomsMatchingType.some(r => r.id === room.id) && roomsMatchingGuestNumber.some(r => r.id === room.id))

How do I select value and options (label names) are invisible from react-select dropdown?

I am new to React. I'm using react-select and I've used the following code. The dropdown is displayed but I'm unable to see names and unable to view after selecting.
<Select
variant="outlined"
margin="normal"
fullWidth
value={this.state.selected}
options={RewardAutomationsList}
name="selected"
onChange={this.handleChange}
placeholder='None'
>
{RewardAutomationsList.map((option) => (
<option key={option.id} value ={option.name} label={option.name}>
{option.name}
</option>
))}
</Select>
handleChange = event => {
this.setState({
selected: event.name
});
};
The RewardAutomationsList looks like this:
RewardAutomationsList:
0:{name: "TEST 1 (INR 100)", id: "123"}
1:{name: "test 2 (INR 250)", id: "456"}
Can someone help with this?
same npm package use like this block code.
import React, { Component } from 'react'
import Select from 'react-select'
const options = [
{ value: 'chocolate', label: 'Chocolate' },
{ value: 'strawberry', label: 'Strawberry' },
{ value: 'vanilla', label: 'Vanilla' }
]
const MyComponent = () => (
<Select options={options} />
)
react-select accepts an array of objects having label and value keys. Your option objects in RewardAutomationsList have id and name keys, so it can't be displayed. You need to change them.
Also, when you subscribe to change events with react-select's onChange prop, the callback function you provide receives the selectedOption, not the event.
The following should work:
const RewardAutomationsList = [
{ label: "TEST 1 (INR 100)", value: "123" },
{ label: "test 2 (INR 250)", value: "456" },
];
class App extends React.Component {
state = {
selected: null,
}
handleChange = (selectedOption) => {
this.setState({
selected: selectedOption,
});
};
render() {
return (
<React.Fragment>
<Select
fullWidth
margin="normal"
name="selected"
onChange={this.handleChange}
options={RewardAutomationsList}
placeholder="None"
value={this.state.selected}
variant="outlined"
/>
{/* It's not necessary and it's only here to show the current state */}
<pre>{JSON.stringify(this.state, null, 2)}</pre>
</React.Fragment>
);
}
}

Why does setState in a loop insert repeated value

I have two logically dependent HTML select components.
The first one represents Districts list and the second one represents corresponding Subdistricts.
When a District is selected, the Subdistricts option array should be altered to represent Subdistricts from the selected District.
Here is how they are represented in components render method:
<div style={{display: "inline-block", marginRight: "20px"}}>
<h style={{fontSize: "20px"}}>District</h>
<br/>
<select id="districts-select-list" onChange={this.updateDistrictData}>
{this.state.districtsSelectOptionsArrayState}
</select>
</div>
<div style={{display: "inline-block"}}>
<h style={{fontSize: "20px"}}>Subdistrict</h>
<br/>
<select id="subdistricts-select-list">
{this.state.subdistrictsSelectOptionsArrayState}
</select>
{this.state.subdistrictsSelectOptionsArrayState}
</div>
As you see options are state dependent.
Here is how update the data:
updateDistrictData(e) {
this.setState({subdistrictsSelectOptionsArrayState : []});
var categoryList = document.getElementById("districts-select-list");
var selectedDistrictId = categoryList.options[categoryList.selectedIndex].value;
if(selectedDistrictId == undefined) {
return;
}
var currentSubdistrictList = this.subdistrictDataArray[selectedDistrictId];
if(currentSubdistrictList != undefined) {
var currentSubdistrictListLength = currentSubdistrictList.length;
if(
currentSubdistrictListLength == undefined ||
currentSubdistrictListLength == 0
) {
return;
}
for(var index = 0; index < currentSubdistrictListLength; index++) {
var currentDistrictObject = currentSubdistrictList[index];
if(currentDistrictObject != undefined) {
var currentSubdistrictId = currentDistrictObject["id"];
var currentSubdistrictName = currentDistrictObject["name"];
console.log("SUBDISTRICT NAME IS : " + currentSubdistrictName);
var currentSubdistrictOption = (
<option value={currentSubdistrictId}>
{currentSubdistrictName}
</option>
);
this.setState(prevState => ({
subdistrictsSelectOptionsArrayState:[
...prevState.subdistrictsSelectOptionsArrayState,
(
<option value={currentSubdistrictId}>
{currentSubdistrictName}
</option>
)
]
}));
}
}
}
}
I call updateDistrictData method after retrieving subdistricts from server and in District select component's onChange method.
When the page is loaded for the first time, the districts and corresponding subdistricts are altered correctly.
But when I change district afterwards using the District select component itself, the subdistricts select component is populated with repetous subdistrict option repeated as many times as the number of subdisctricts in the current district.
What am I doing wrong?
The problem is caused by the use of vars (currentSubdistrictId and currentSubdistrictName) in a closure (the setState callBack).
=> because of the var declaration, the last value taken by currentSubdistrictId and currentSubdistrictName were used for all options.
Closures are really tricky in js when used with vars (sort of global scope).
Since you're using es6, you should properly use let (modified within a block like index in the for loop) and const (set only once when declared in a block) variables declaration and never use var (sort of global scope).
class App extends React.Component {
districtDataArray = [
{ name: 'A', id: 0 },
{ name: 'B', id: 1 },
{ name: 'C', id: 2 },
]
subdistrictDataArray = [
[
{ name: 'AA', id: 0 },
{ name: 'AB', id: 1 },
{ name: 'AC', id: 2 },
],
[
{ name: 'BA', id: 0 },
{ name: 'BB', id: 1 },
{ name: 'BC', id: 2 },
],
[
{ name: 'CA', id: 0 },
{ name: 'CB', id: 1 },
{ name: 'CC', id: 2 },
],
]
state = {
districtsSelectOptionsArrayState: this.districtDataArray.map(d => (
<option value={d.id}>
{d.name}
</option>
)),
subdistrictsSelectOptionsArrayState: [],
}
constructor(props) {
super(props);
this.updateDistrictData = this.updateDistrictData.bind(this);
}
updateDistrictData(e) {
this.setState({subdistrictsSelectOptionsArrayState : []});
const categoryList = document.getElementById("districts-select-list");
const selectedDistrictId = categoryList.options[categoryList.selectedIndex].value;
if(!selectedDistrictId) {
return;
}
const currentSubdistrictList = this.subdistrictDataArray[selectedDistrictId];
if(currentSubdistrictList) {
const currentSubdistrictListLength = currentSubdistrictList.length;
if(!currentSubdistrictListLength) {
return;
}
for(let index = 0; index < currentSubdistrictListLength; index++) {
// use const for block level constant variables
const currentDistrictObject = currentSubdistrictList[index];
if(currentDistrictObject) {
// use const for block level constant variables
const currentSubdistrictId = currentDistrictObject["id"];
const currentSubdistrictName = currentDistrictObject["name"];
console.log("SUBDISTRICT NAME IS : " + currentSubdistrictName);
const currentSubdistrictOption = (
<option value={currentSubdistrictId}>
{currentSubdistrictName}
</option>
);
this.setState(prevState => ({
subdistrictsSelectOptionsArrayState:[
...prevState.subdistrictsSelectOptionsArrayState,
(
<option value={currentSubdistrictId}>
{currentSubdistrictName}
</option>
)
]
}));
}
}
}
}
render() {
return (
<div>
<div style={{display: "inline-block", marginRight: "20px"}}>
<h style={{fontSize: "20px"}}>District</h>
<br/>
<select id="districts-select-list" onChange={this.updateDistrictData}>
{this.state.districtsSelectOptionsArrayState}
</select>
</div>
<div style={{display: "inline-block"}}>
<h style={{fontSize: "20px"}}>Subdistrict</h>
<br/>
<select id="subdistricts-select-list">
{this.state.subdistrictsSelectOptionsArrayState}
</select>
{this.state.subdistrictsSelectOptionsArrayState}
</div>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root" />
Also, the way you are updating the state in updateDistrictData is very inefficient (n + 1 setStates, n in a loop, n being the number of subdistricts).
You should compute the final state in a variable and set it all at once when computation is done.
While my implementation explains what was wrong with your code without altering it too much, Jared's answer below is a very good example of how it could be done cleaner.
This should solve the original problem, but this should hopefully solve a lot of other problems...
Your display code should look something like this:
<div style={{ display: "inline-block", marginRight: "20px" }}>
<h style={{ fontSize: "20px" }}>District</h>
<br />
<select id="districts-select-list" onChange={this.updateDistrictData}>
{this.state.districts.map(({ name, id }) => (
<option value={id} key={id}>{name}</option>
))}
</select>
</div>
<div style={{ display: "inline-block" }}>
<h style={{ fontSize: "20px" }}>Subdistrict</h>
<br />
<select id="subdistricts-select-list">
{
this.state
.subdistricts
.filter(({ districtId }) => districtId === this.state.selectedDistrictId)
.map(({ id, name }) => <option value={id} key={id}>{name}</option>)
}
</select>
</div>
And your update code now looks like this:
updateDistrictData (e) {
this.setState({ selectedDistrictId: e.target.value });
}
There's no point in storing all of those JSX react nodes in state, as long as you use the unique id for the key property React won't do unnecessary re-renders.
I would further recommend that you move the lists out of state entirely, and pass them in as props from a stateful parent component. Generally it's better to have components that display information to the user to be totally stateless and have the manipulation of state higher up the chain.

How do I create a dynamic drop down list with react-bootstrap

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>

Categories