I'm creating JSX element with the onClick on the button:
<div onClick={() => addWeek()} >
add week
</div>
then I update state (add new items to array) :
//my state that has one object initially (I want to add more)
const [weekly, setweekly] = useState([
{
id: 'n1',
day_week: null,
start_time: null,
end_time: null,
},
]);
const addWeek = () => {
setweekly([
...weekly,
{
id: `n${weekly.length + 1}`,
day_week: null,
start_time: null,
end_time: null,
},
]);
}
after I create JSX element I have an onChange event on that element :
NOTE: This element created with onClick and I have two objects inside my state now.
<select
onChange={(event) => handleWeekly(event)}
id={`n${weekly.length + 1}`}
>
//Some options
</select>
but in here I can't access the updated state I get one object.
const handleWeekly = (event) => {
// I get one object
console.log(weekly);
};
CODE SAND BOX :
https://codesandbox.io/s/strange-nightingale-l6qg3?file=/src/App.js:0-1372
I would approach this problem differently. Instead of putting markup in the state you can map through your data and render your components this way:
export default function App() {
const [weeks, setWeek] = useState([
{
id: "n1",
day_week: null,
start_time: null,
end_time: null
}
]);
const addWeek = () => {
setWeek([
...weeks,
{
id: `n${weeks.length + 1}`,
day_week: null,
start_time: null,
end_time: null
}
]);
};
const handleWeekly = (event) => {
console.log(weeks);
};
return (
<div className="App">
<div onClick={() => addWeek()}>add week</div>
{weeks.map((week) => {
return (
<select
onChange={(event) => handleWeekly(event)}
id={`n${week.id}`}
key={`n${week.id}`}
>
<option value="volvo">Volvo</option>
<option value="saab">Saab</option>
<option value="mercedes">Mercedes</option>
<option value="audi">Audi</option>
</select>
);
})}
</div>
);
}
It's easier to keep track of your state this way.
Sandbox Example
Related
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
I have JSON file like this
[
{
"id": 1,
"country": "Afghanistan",
"city": ["Eshkashem","Fayzabad","Jurm","Khandud"]
},
{
"id": 2,
"country": "Italy",
"city": ["Milano","Rome","Torino","Venezia"]
}
]
and I want to iterate through array placed in the city. Idea is to have two selects, where the first select is reserved for countries and the second is reserved for cities. Whenever the user selects a country, I want to populate the second select with a list of cities. Problem is that I receive only one array of all cities for that country. Here is my code:
export default class DiffCountries extends Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,
contacts: [],
selectedCountry: [],
selectedCity: []
}
}
onChangeHandler = (event) => {
const test = CountriesData[event.target.value - 1];
this.setState({
selectedCountry: test,
selectedCity: this.state.selectedCountry.city
})
console.log(this.state.selectedCity);
}
render() {
const { contacts } = this.state;
return (
<div>
<select name="" id="" onChange={this.onChangeHandler}>
{CountriesData.map(item => {
const { id, country } = item;
return <option key={id} value={id}>{country}</option>
})}
</select>
<select name="" id="">
{this.state.selectedCountry !== undefined ?
<option value="">{this.state.selectedCountry.city}</option> :
null
}
</select>
</div>
<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>
And here is the screenshot of my problem
Thank you in advance!
You need to use map() on the city array.
<select name = "" id = "" > {
this.state.selectedCountry !== undefined ?
this.state.selectedCountry.city.map((x,i) => <option value={x} key={i}>{x}</option>)
:null
}
</select>
You need to iterate through the array.
this.state.selectedCountry.city.map((city, index) => {
return <option value={city} key={index}>{city}</option>
})
Be aware, that using the index as a key is considered an anti pattern. You could use the name of the city as a key as well. E.g.:
this.state.selectedCountry.city.map(city => {
return <option value={city} key={city}>{city}</option>
})
edit to add link to mdn docs as suggested in comments: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
Example:
const CountriesData = [
{
id: 1,
country: 'Afghanistan',
city: ['Eshkashem', 'Fayzabad', 'Jurm', 'Khandud'],
},
{
id: 2,
country: 'Italy',
city: ['Milano', 'Rome', 'Torino', 'Venezia'],
},
];
class DiffCountries extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedCountry: null,
};
}
onChangeHandler = event => {
const selectedCountry = CountriesData[event.target.value - 1];
this.setState({
selectedCountry,
});
};
render() {
const { selectedCountry } = this.state;
return (
<div>
<select
name="country"
defaultValue="country"
onChange={this.onChangeHandler}
>
<option disabled value="country">
Select country
</option>
{CountriesData.map(({ id, country }) => (
<option key={id} value={id}>
{country}
</option>
))}
</select>
{selectedCountry && (
<select name="city" defaultValue="city">
<option disabled value="city">
Select city
</option>
{selectedCountry.city.map(item => (
<option key={item} value={item}>
{item}
</option>
))}
</select>
)}
</div>
);
}
}
ReactDOM.render(<DiffCountries />, document.getElementById('container'));
I'm trying to make a react component that can filter a list based on value chosen from a drop-down box. Since the setState removes all data from the array I can only filter once. How can I filter data and still keep the original state? I want to be able to do more then one search.
Array list:
state = {
tree: [
{
id: '1',
fileType: 'Document',
files: [
{
name: 'test1',
size: '64kb'
},
{
name: 'test2',
size: '94kb'
}
]
}, ..... and so on
I have 2 ways that I'm able to filter the component once with:
filterDoc = (selectedType) => {
//way #1
this.setState({ tree: this.state.tree.filter(item => item.fileType === selectedType) })
//way#2
const myItems = this.state.tree;
const newArray = myItems.filter(item => item.fileType === selectedType)
this.setState({
tree: newArray
})
}
Search component:
class SearchBar extends Component {
change = (e) => {
this.props.filterTree(e.target.value);
}
render() {
return (
<div className="col-sm-12" style={style}>
<input
className="col-sm-8"
type="text"
placeholder="Search..."
style={inputs}
/>
<select
className="col-sm-4"
style={inputs}
onChange={this.change}
>
<option value="All">All</option>
{this.props.docTypes.map((type) =>
<option
value={type.fileType}
key={type.fileType}>{type.fileType}
</option>)}
</select>
</div>
)
}
}
And some images just to get a visual on the problem.
Before filter:
After filter, everything that didn't match was removed from the state:
Do not replace original data
Instead, change what filter is used and do the filtering in the render() function.
In the example below, the original data (called data) is never changed. Only the filter used is changed.
const data = [
{
id: 1,
text: 'one',
},
{
id: 2,
text: 'two',
},
{
id: 3,
text: 'three',
},
]
class Example extends React.Component {
constructor() {
super()
this.state = {
filter: null,
}
}
render() {
const filter = this.state.filter
const dataToShow = filter
? data.filter(d => d.id === filter)
: data
return (
<div>
{dataToShow.map(d => <span key={d.id}> {d.text}, </span>)}
<button
onClick={() =>
this.setState({
filter: 2,
})
}
>
{' '}
Filter{' '}
</button>
</div>
)
}
}
ReactDOM.render(<Example />, 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>
<body>
<div id='root' />
</body>
Don't mutate local state to reflect the current state of the filter. That state should reflect the complete available list, which should only change when the list of options changes. Use your filtered array strictly for the view. Something like this should be all you need to change what's presented to the user.
change = (e) => {
return this.state.tree.filter(item => item.fileType === e.target.value)
}
I want to store select default value if user not touch it in ReactJs. How is that possible?
<select onChange={this.valSelected.bind(this)}>
{currencies.map(function(name, index){
return <option value={name}>{name}</option>;
})}
</select>
and
valSelected(event){
this.setState({
valSelected: event.target.value
});
}
You can just add a value property to the select element, set by your state.
<select value={this.state.valSelected} onChange={this.valSelected.bind(this)}>
{currencies.map(function(name, index){
return <option value={name}>{name}</option>;
})}
</select>
This is described here in the react docs: Doc Link
Then set a default state for the component, either in the constructor or with getInitialState: What is the difference between using constructor vs getInitialState in React / React Native?
Use defaultValue to select the default value.
const statusOptions = [
{ value: 1, label: 'Publish' },
{ value: 0, label: 'Unpublish' }
];
const [statusValue, setStatusValue] = useState('');
const handleStatusChange = e => {
setStatusValue(e.value);
}
return(
<>
<Select options={statusOptions} defaultValue={[{ value: published, label: published == 1 ? 'Publish' : 'Unpublish' }]} onChange={handleStatusChange} value={statusOptions.find(obj => obj.value === statusValue)} required />
</>
)
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>