I have this Dropdown menu instance:
<Dropdown
selection
options={this.state.options}
search
value={value}
onChange={this.handleChange}
onSearchChange={this.handleSearchChange}
/>
and when my backend returns response, which is then set as state and it is structured like this:
"options": [
{
"text": "New York,All Airports (NYC) , USA",
"value": "NYC"
},
{
"text": "New York,Newark Liberty Intl (EWR), USA",
"value": "EWR"
},
{
"text": "New York,John F Kennedy (JFK), USA",
"value": "JFK"
},
{
"text": "New York,La Guardia (LGA), USA",
"value": "LGA"
}
]
...I get this warning:
Warning: flattenChildren(...): Encountered two children with the same
key, 1:$BLZ. Child keys must be unique; when two children share a
key, only the first child will be used.
in select (created by Dropdown)
in div (created by Dropdown)
in Dropdown (created by SearchForm)
How do I add keys to these elements to prevent this warning?
So looking at the code for the Semantic UI source for the dropdown component, the render options function converts your passed in options into a array of DropdownItem components:
renderOptions = () => {
const { multiple, search, noResultsMessage } = this.props
const { selectedIndex, value } = this.state
const options = this.getMenuOptions()
if (noResultsMessage !== null && search && _.isEmpty(options)) {
return <div className='message'>{noResultsMessage}</div>
}
const isActive = multiple
? optValue => _.includes(value, optValue)
: optValue => optValue === value
return _.map(options, (opt, i) => (
<DropdownItem
key={`${opt.value}-${i}`}
active={isActive(opt.value)}
onClick={this.handleItemClick}
selected={selectedIndex === i}
{...opt}
// Needed for handling click events on disabled items
style={{ ...opt.style, pointerEvents: 'all' }}
/>
))
}
the key for this array is set by taking the value prop and appending the index to it:
key={`${opt.value}-${i}`}
which should always be unique since the index is used but there is another part of the code for hidden inputs
renderHiddenInput = () => {
debug('renderHiddenInput()')
const { value } = this.state
const { multiple, name, options, selection } = this.props
debug(`name: ${name}`)
debug(`selection: ${selection}`)
debug(`value: ${value}`)
if (!selection) return null
// a dropdown without an active item will have an empty string value
return (
<select type='hidden' aria-hidden='true' name={name} value={value} multiple={multiple}>
<option value='' />
{_.map(options, option => (
<option key={option.value} value={option.value}>{option.text}</option>
))}
</select>
)
}
in this one the key is set to only the value, not the value plus index.
<option key={option.value} value={option.value}>{option.text}</option>
this might be your problem, if you have duplicate values then the key will not be unique. Double check the options list to make sure you don't have duplicate values.
Related
I am creating a dropdown edit form where on edit user will able to see the option value as selected from props on edit. I tried with defaultValue and using selected but it seems my conditions are wrong.
Any suggestions which I can utilized to check the props value in option and display as selected on dropdown when user click edit.
Here selectedPropsName is what we are getting from props and I want to map and display this as selected.
Thanks...
Code
const results = [
{
'Name': 'apple',
'description': 'apple is good for health'
},
{
'Name': 'mango',
'description': 'mango is very juicy'
}
]
const props = {
'Name': 'apple'
}
const CreateForm=()=> {
const [spaceName, setSpaceName] = React.useState('');
const [, setSpaceDescriptionName] = React.useState('');
const handleChangeSpaceName = (event: React.ChangeEvent<HTMLInputElement>) => {
setSpaceName(event.target.value);
setSpaceDescriptionName(event.target.value)
};
const selectedPropsName = props.Name
return (
<>
<div className='create-dropdown'>
<TextField
className='create-textfield'
id='create-textfield'
select={true}
label='create Name'
value={spaceName}
onChange={handleChangeSpaceName}
SelectProps={{
native: true,
}}
variant='standard'
>
{results && results.length > 0 && results.map((optionNames:any) => (
<option key={optionNames.Name}
className='textfield-option'
value={`${optionNames.Name}, ${optionNames.description}`}>
{optionNames.Name}
</option>
))}
</TextField>
</div>
</>
)
}
If I understand you correctly, you should be able to check if optionNames.Name equals selectedPropsName and then set the option's selected attribute to true, conditionally.
So,
{results && results.length > 0 && results.map((optionNames:any) => (
<option key={optionNames.Name}
className='textfield-option'
value={`${optionNames.Name}, ${optionNames.description}`}>
{optionNames.Name}
</option>
))}
becomes
{results && results.length > 0 && results.map((optionNames:any) => (
<option key={optionNames.Name}
selected={optionNames.Name === selectedPropsName ? true : false}
className='textfield-option'
value={`${optionNames.Name}, ${optionNames.description}`}>
{optionNames.Name}
</option>
))}
EDIT:
My mistake. I've taken another look. It looks like you are using Material-UI and so, the select component is controlled. You would have to set the initial value through the spaceName hook. And you would have to change the props.Name variable to include a "name, description" since that is what your option value={} field is expecting. So, these changes should make it work:
const props = {
Name: "mango, mango is very juicy",
};
const [spaceName, setSpaceName] = React.useState(props.Name);
I have created a list of objects and each list item has input within:
{flowers_data?.map((flower) => {
return (
<>
<div className={classes.Nested_Flower_Container} key={flower.id}>
<div className={classes.Nested_Flower_Name}>
{flower.name}
</div>
<div className={classes.Nested_Flower_Input} style={{ marginRight: '0.2em' }}>
<TextField
id="Amount"
label="Amount"
variant="outlined"
size="small"
type="number"
onChange={(e) => {
setAmount(e.target.value);
handleAddList(e.target.value, flower);
}}
className={classes_2.root}
/>
</div>
</div>
</>)
})}
How can I add an object that has a value in input to an array? I tried to do this using a function that I created, but each time I change one element's target.value and move on to the next item to change its input value, there is only one element in the array with the latest target.value. And after modifying the inputs, when I try to output the values outside that function with e.g. a button, the add_flowers_tab array is empty.
handleAddList function:
let temp_flower: Flower;
let add_flowers_tab: Flower[] = [];
const handleAddList = (targetValue: string, flower: Flower) => {
temp_flower = {
"id": flower.id,
"name": flower.name,
"price": flower.price,
"amount": Number(targetValue),
"creation_date": flower.creation_date
}
if (targetValue === '') {
/* Delete flower when input is empty */
add_flowers_tab.forEach(tabFlower => {
if (tabFlower.id === temp_flower.id) {
const indexOfDelete = add_flowers_tab.indexOf(tabFlower);
add_flowers_tab.splice(indexOfDelete, 1);
}
})
}
if (targetValue !== '') {
/* Add flower to tab when input has value */
if (add_flowers_tab.length > 0) {
/* When input changes, delete flower with old input value and add the new one */
add_flowers_tab.forEach(tabFlower => {
if (tabFlower.id === temp_flower.id) {
const indexOfDelete = add_flowers_tab.indexOf(tabFlower);
add_flowers_tab.splice(indexOfDelete, 1);
add_flowers_tab.push(temp_flower);
}
})
}
else {
/* Add new flower as a first element in the array */
add_flowers_tab.push(temp_flower);
}
/*
Displays an array with only the most recently added temp_flower, even though
several inputs in list have values
*/
console.log(add_flowers_tab);
}
}
Here is the minimum solution for generating a list on Inputs that each update a single element in a list on state.
export const ManyInputs = ({inputs}) => {
const [list, setList] = useState(inputs);
const setIndividual = (value, id) =>
// Update a single field in the object in the list that matches the given id
setList(list.map(l => l.id === id ? ({...l, value}) : l));
return list.map(i =>
// Send the id back to help match the object
<TextField onChange={(e) => setIndividual(e.target.value, i.id)} />
);
};
You can have one React state, which has an array of objects that you enter through your "mapped" component.
const [tmpAmount, setTmpAmount] = React.useState<{flower:Flower,amount:number}[]>([])
// fill the temporary state with generated flowers only once
React.useEffect(()=>{
setTmpAmount(flowers_data.map((flwr) =>{flower:flwr, amount:0}))
},[])
and replace the onChange()
onChange={(e) => {
// find existing flower to edit the amount of it
let indexToChange = flowers_data.findIndex((element) => element.id === flower.id)
// update and replace the array variable with updated object
setAmount2((prev) =>
[
...prev.slice(0, indexToChange),
{
...prev[indexToChange],
amount: e.target.value,
},
...prev.slice(indexToChange + 1),
]);
}
You can also check if the array changes and print it:
React.useEffect(()=>console.log(tmpAmount),[tmpAmount])
I'm working on a Car website and I added a simple json file with data of each car brand and their models.
[
{"id": "1", "brand": "Seat", "models": ["Alhambra", "Altea", "Altea XL", "Arosa", "Cordoba", "Cordoba Vario", "Exeo", "Ibiza", "Ibiza ST", "Exeo ST", "Leon", "Leon ST", "Inca", "Mii", "Toledo"]},
{"id": "2", "brand": "Renault", "models": ["Captur", "Clio", "Clio Grandtour", "Espace", "Express", "Fluence", "Grand Espace", "Grand Modus", "Grand Scenic", "Kadjar", "Kangoo", "Kangoo Express", "ZoƩ"]},
{"id": "3", "brand": "Audi", "models": ["A1", "A2", "A3", "A4", "A4 Allroad", "A4 Avant", "A5", "A6", "A7", "A8", "A8 Long", "Q3", "Q5", "Q7", "R8", "RS4 Cabriolet", "RS4/RS4 Avant", "RS5", "RS6 Avant", "RS7"]}
]
I added a dropdown menu where when I select a specific brand it will show me only the models of that brand. For example, if I Pick Audi I will only get "A1, A2, A3, A4, etc.". However, right now when I pick my car brand I get all those brand models in one line. How can I make it so each model is on a separate line instead of all clustered in one?
Here are pictures of how it looks now:
Image 1
Image 2
My code
import React, { Component } from 'react';
import ReactDOM from "react-dom";
export default class simpleData extends Component {
constructor(props) {
super(props);
this.state = {
cars: [],
models: ""
};
this.handleSubmitCar = this.handleSubmitCar.bind(this);
}
handleSubmitCar(event) {
alert("Your selected value is: " + this.state.models);
event.preventDefault();
}
handleChangeCar = event => {
this.setState({ models: event.target.value });
};
getUnique(arr, comp) {
const unique = arr
//store the comparison values in array
.map(e => e[comp])
// store the keys of the unique objects
.map((e, i, final) => final.indexOf(e) === i && i)
// eliminate the dead keys & store unique objects
.filter(e => arr[e])
.map(e => arr[e]);
return unique;
}
componentDidMount() {
const cars = require("./cars.json");
this.setState({ cars: cars });
}
render() {
const uniqueCar = this.getUnique(this.state.cars, "brand");
const cars = this.state.cars;
const models = this.state.models;
const filterDropdown = cars.filter(function(result) {
return result.brand === models;
});
return (
<div>
<form onSubmit={this.handleSubmitCar}>
<br />
<br />
<label>
Car brand
<select
value={this.state.models}
onChange={this.handleChangeCar}
>
{uniqueCar.map(models => (
<option key={models.id} value={models.brand}>
{models.brand}
</option>
))}
</select>
</label>
<input type="submit" value="Submit" /><br/>
Car Model
<select>
{filterDropdown.map(models => (
<option key={models.id}>
{models.models}
</option>
))}
</select>
</form>
</div>
);
}
}
In your map function
{filterDropdown.map(models => (
<option key={models.id}>
{models.models}
</option>
))}
You need a way to map the models that the user selected, the selected car's models, which is models.models
Instead what's happening is you're filtering the selected car in the original json, and returning any value that matches the selected car, that returns an array, which stores only 1 object, the selected car itself, and you're mapping over that array.
that means in your map function, you can access the id, brand, model properties, but because you can only loop once, you can't loop over the models themselves.
What you need to do, is have a seperate state, for the selected models. and map over that instead. the selected models value would change whenever a car gets reselected
handleChangeCar = event => {
// Returns an array with objects that pass the filter condition,
// meaning it will return an array with only one object
const returnSelectedBrand = this.state.cars.filter(function(result) {
return result.brand === event.target.value;
});
this.setState({
selectedBrand: event.target.value,
selectedBrandModels: returnSelectedBrand[0].models
});
};
and map over that instead.
<select>
{
// if selectedBrandModel is truthy, map it
selectedBrandModels && selectedBrandModels.map((model, i) => {
return (
// you might need to add an id for each model, we're using
// the loop index for now
<option key={i}>
{model}
</option>
)
})
}
</select>
Here's the full working code
import React, { Component } from 'react';
export default class simpleData extends Component {
constructor(props) {
super(props);
this.state = {
cars: [],
// Renamed to 'selectedBrand' instead of model
selectedBrand: "",
// added 'selectedBrandModels to keep track of the selected brand's models
selectedBrandModels: []
};
this.handleSubmitCar = this.handleSubmitCar.bind(this);
}
handleSubmitCar(event) {
alert("Your selected value is: " + this.state.models);
event.preventDefault();
}
handleChangeCar = event => {
// Returns an array with objects that pass the filter condition, meaning it
// will return an array with only one object
const returnSelectedBrand = this.state.cars.filter(function(result) {
return result.brand === event.target.value;
});
this.setState({
selectedBrand: event.target.value,
selectedBrandModels: returnSelectedBrand[0].models
});
};
getUnique(arr, comp) {
const unique = arr
//store the comparison values in array
.map(e => e[comp])
// store the keys of the unique objects
.map((e, i, final) => final.indexOf(e) === i && i)
// eliminate the dead keys & store unique objects
.filter(e => arr[e])
.map(e => arr[e]);
return unique;
}
componentDidMount() {
const cars = require("./cars.json");
this.setState({ cars: cars });
}
render() {
const uniqueCar = this.getUnique(this.state.cars, "brand");
// used object deconstructoring for clearer syntax
const { selectedBrandModels } = this.state;
return (
<div>
<form onSubmit={this.handleSubmitCar}>
<br />
<br />
<label>
Car brand
<select
value={this.state.models}
onChange={this.handleChangeCar}
>
{uniqueCar.map(models => (
<option key={models.id} value={models.brand}>
{models.brand}
</option>
))}
</select>
</label>
<input type="submit" value="Submit" /><br/>
Car Model
<select>
{
// if selectedBrandModel is truthy, map it
selectedBrandModels && selectedBrandModels.map((model, i) => {
return (
// you might need to add an id for each model, we're using
// the loop index for now
<option key={i}>
{model}
</option>
)
})
}
</select>
</form>
</div>
);
}
}
EDIT: I realised you could also just map the selected car object directly instead of making a separate state for its models, something like:
selectedCar &&
selectedCar.models.map((model, i) => {
// or selectedCar[0].models.map if its an array
return (
// you might need to add an id for each model, we're using
// the loop index for now
<option key={i}>
{model}
</option>
)
})
}
I want this select to have an object value, this works but I can't show the title of the selected object, does anyone know why?
my code is:
const [categoria, setCategoria] = useState("");
const litaCategorias = [
{
"id": 2,
"titulo": "test",
"descripcion": "descripcion",
}];
const handleChangeCategoria = (value) => {
setCategoria(value)
console.log(value)
}
<Select
fullWidth
variant="outlined"
value={categoria.titulo}
onChange={e => handleChangeCategoria(e.target.value)}
labelWidth={0}
placeholder={"Seleccione un destino"}
>
{litaCategorias.map(categoria => {
return (
<option
value={categoria}
key={categoria.titulo}
>
{categoria.titulo}
</option>
);
})}
</Select>
I have double checked the Select component in material-ui. It provides props api value with description:
If the value is an object it must have reference equality with the option in order to be selected. If the value is not an object, the string representation must match with the string representation of the option in order to be selected.
You you should memorize you array litaCategorias. to make reference equality.
Should use useMemo to memorized array.
const litaCategorias = useMemo(() => [ { "id": 2, "titulo": "test", "descripcion": "descripcion", }], []);
I think it's because you're setting the param in your callback in the .map function to categoria which is defined in the state and is initialized to an empty string. This line here - {litaCategorias.map(categoria => {. The option value in the select dropdown is not getting the correct object because of this.
Try something like this instead:
import React, { useState } from "react";
export default function App() {
const [categoria, setCategoria] = useState("");
const litaCategorias = [
{ id: 1, titulo: "test", descripcion: "descripcion" },
{ id: 2, titulo: "test2", descripcion: "descripcion2" }
];
const handleChangeCategoria = (value) => {
setCategoria(value);
console.log(value);
};
return (
<select
value={categoria.titulo}
onChange={(e) => handleChangeCategoria(e.target.value)}
>
{litaCategorias.map((cat) => {
return (
<option value={cat.titulo} key={cat.id}>
{cat.titulo}
</option>
);
})}
</select>
);
}
https://codesandbox.io/s/wild-mountain-0r4p6?file=/src/App.js
Given the following data, how can I get the birds name and push it (Using the add button) to a new array to be displayed in another div (Using react es6)? So basically I want a user to click a bird from the semantic dropdown and display it in a different div for example shown below. This is probably simple but I can't seem to find a way to it when I'm using Semantic elements. Do I need to use onChange?
I need to to do this in a class I am exporting (react) (just havent shown the class/constructor/state definitions)
<div>
<p>How can i display 'Bird_Name' here?<p>
</div>
addClick = () => {
}
const {
Button,
Container,
Divider,
Dropdown,
Header,
Message,
Segment,
} = semanticUIReact
const birds = [
{
"value": "001",
"Bird_Name": "Eurasian Collared-Dove"
},
{
"value": "002",
"Bird_Name": "Bald Eagle"
},
{
"value": "003",
"Bird_Name": "Cooper's Hawk"
},
];
const options = birds.map(({ ID, Bird_Name }) => ({ value: ID, text: Bird_Name }))
const App = () => (
<Container>
<Divider hidden />
<Header as='h1'>Semantic-UI-React</Header>
<Dropdown
placeholder='Select...'
selection
search
options={options}
renderLabel={({ Bird_Name }) => 1}
/>
<button className="ui primary button add" onClick={this.addClick}>Add</button>
</Container>
)
// ----------------------------------------
// Render to DOM
// ----------------------------------------
const mountNode = document.createElement('div')
document.body.appendChild(mountNode)
ReactDOM.render(<App />, mountNode)
So, what you basically want is the onChange function which will display.
<Dropdown
placeholder='Select...'
selection
search
options={options}
renderLabel={({ Bird_Name }) => 1}
onChange={this.getBird}
/>
and make a getBird function
getBird = (event, {value}) => {
console.log(value);
let bird_name = event.target.textContent;
console.log(bird_name);
}
The value and text variable in the getBird function are basically the value and bird_name of the selected bird from the dropdown.