I mapping over data which displays different select option dropdowns. I need my onChange function to be on the select tag to capture the selected option, but the select tag is before the function is initialized so I'm not sure how to go about passing the function into it.
<select onChange={onChange}> <--- onChange function cant be passed here
{options.map((attribute) => {
function onChange(event) {
const newSelected = { ...selected };
newSelected[attribute.title] = event.target.value
}
return (
<option value={attribute.index}>{attribute.value}</option>
)
})}
</select>
Define your onChange so that it uses the information on the event object it receives to find the right entry in the list, something like this:
<select onChange={event => {
const attribute = options[event.currentTarget.value];
const newSelected = {
...selected,
[attribute.title]: event.currentTarget.value,
};
// ...
}}>
{options.map((attribute, index) => {
return (
<option key={attribute.value} value={index}>{attribute.value}</option>
)
})}
</select>
It's hard to be sure the details of that are right without knowing the structure of options or what you want newSelected to be, but I think I've mostly inferred it from the question's code. Note that I changed attribute.index to index in the map call, and that I added a key (you need key on any array you render), assuming that attribute.value is unique.
That said, if attribute.value is unique, I think I'd use it instead of index or attribute.index:
<select onChange={event => {
const attribute = options.find(({value}) => value == event.currentTarget.value);
const newSelected = {
...selected,
[attribute.title]: attribute.index,
};
// ...
}}>
{options.map(({value}) => {
return (
<option key={value} value={value}>{value}</option>
)
})}
</select>
I think I sort of see why you're doing it the way you're doing it, so let me try to address how to handle it differently:
First of all, you're doing it because the onChange function needs access to the attribute.title value. So for you, you're thinking the easiest way to get that value is to define the function in the .map function, where you have access to each individual attribute as it's coming through.
You can't, though. As I said in my comment: It conceptually doesn't make sense. <select onChange={onChange}> implies a singular onChange function, but you're defining a new onChange function in every iteration of .map.
So what you need to do is define onChange before your render function, and then inside that you have to find the matching attribute inside the options array, so that you can then use attribute.title as you like.
[edit] TJ Crowder gives a specific code example of how to do that.
Related
I've got a 'list' component, which allows you to add an item to a state array, and then display it from the state afterwards. These list items can then be removed by the user afterwards (or should be able to).
There's four state props in this component:
currentList: content in the input that's to be added to the list array
setCurrentList: what's used to change the content in the input
fullList: the full array list
setFullList: used to add the currentList content to the array, and removed
I'm using .filter() to create a copy of the state array, and then set the state afterwards in this function:
const deleteFromList = (e) => {
console.log("Delete button pressed")
console.log(e)
let fullList = props.fullListState
let setFullList = props.setFullListState
let filteredArray = fullList.filter(item => item)
setFullList(filteredArray)
}
However, every time I execute this function (i.e. when the delete button is pressed), it just creates a loop and the first two console.logs are just repeatedly done.
This is the full return function itself:
<>
<label className="setup-jobs-label">{props.label}</label>
<div className="setup-jobs-input-container">
<input className="setup-jobs-alt-input" type="text" onChange={onChange} value={props.currentListState} />
<button className="setup-jobs-add-button" onClick={addToList}>Add</button>
</div>
{ props.fullListState === [] ? null : props.fullListState.map(x => {
return <div className="setup-jobs-input-container" key={props.fullListState[x]}>
<p className="setup-jobs-input-paragraph">{x}</p>
<button className="setup-jobs-delete-button" onClick={deleteFromList(x)}>Delete</button>
</div>
}) }
</>
The important bit is the bottom conditional render, which checks to see if the state array is empty, and if so, not display anything. If it isn't, then it returns null.
Any advice would be appreciated - not sure what I'm doing wrong in the filter function.
In your onClick handler, you pass the result of the execution of deleteFromList, you should pass a reference to this function instead :
// note the '() =>'
<button className="setup-jobs-delete-button" onClick={() => deleteFromList(x)}>Delete</button>
See https://reactjs.org/docs/handling-events.html for more details about this.
Beside this, your filter logic does not seem right :
// this line only removes falsy values, but not the "e" values
let filteredArray = fullList.filter(item => item)
// you should implement something like this
let filteredArray = fullList.filter(item => [item is not "e"])
// this should work as we work on objects references
let filteredArray = fullList.filter(item => item !== e)
I am working in react on a ruby on rails backend using antd. I have an antd form that passes in options from the controller in the backend. The point of this form item is to get an item id number, but I would also like to pass in a couple other attributes that are included in the controller.
The data works for me, so in the code example if I change <Option key={"item_" + o.id} value={o.id}> to <Option key={"item_" + o.id} value={o.attribute2}> I correctly get the other attribute I want. The problem is I want id, attribute2, and attribute3 to all pass in.
Is it possible for an antd form item to have multiple values. In my code example the commented out lines are things I have tried. I tried doing an array like value={o.id, o.attribute2, o.attribute3} and then extracting them with value.attribute2 or value[1], but nothing seems to work.
Or if I can pass in the data as just information like have value={} and then otherdata = {} that would work too, but that didn't seem to work either.
Below is my code,
<Form.Item name="item_id" label={"item"} style={{ marginBottom: "0px" }}>
<Select style={{width:"200px"}} onChange={(value, attribute2) => {
id_variable = value;
// id_variable = value[0];
// attribute2_variable = value.attribute2;
// attribute2_variable = value[1];
// attribute2_variable = attribute2
}}>
{options.tanks.map((o) => <Option key={"item_" + o.id} value={o.id}>{o.label}</Option>)}
{/* {options.tanks.map((o) => <Option key={"item_" + o.id} value={o.id, o.attribute2, o.attribute3} attribute2={o.attribute2}>{o.label}</Option>)} */}
</Select>
</Form.Item>
Figured out what I was doing wrong. In case anyone sees this and has a similar problem: I wasn't making value an array correctly.
value={o.id, o.attribute2, o.attribute3}
should be
value={[o.id, o.attribute2, o.attribute3]}
Then you can call
<Select onChange={(value) => {
id_variable = value[0];
attribute2_variable = value[1];
attribute2_variable = value[2];
Basically I have a dropdown in which each option has an attribute corresponding to the ID of an image. My goal is to go to that image when an option is chosen. For that I am trying to use:
const myCustomNext = () => {
flkty.selectCell(somevar)
};
somevar is initially set to #someid, when I click my button, it goes to the cell with that ID perfectly.
The issue starts once I update the value of somevar. As soon as I do and then click the button, I get the following Error:
"Cannot read property 'selectCell' of null"
I logged both the initital somevar and the updated one. Other than the ID itself, they are absolutely identical so I have no clue where I am going wrong. I tried switchen the static and reloadOnUpdate settings but that didn't help.
Here a more complete example that might show better what I am trying to do:
const FlickTest = () => {
const [varimg, setVarimg] = useState("#cG9zdDoxNA");
let flkty = null;
function setVariant(e) {
let index = e.target.selectedIndex;
let optionElement = e.target.childNodes[index]
let option = optionElement.getAttribute('data-imgid');
setVarimg(`#${option}`);
}
const myCustomNext = () => {
flkty.selectCell(varimg)
};
return (
<>
<button onClick={myCustomNext}>My custom next button</button>
<select onChange={setVariant}>
{variants.map((variant) =>
<option data-imgid={variant.gallerie[0].id} value={variant.farbe} key={variant.farbe}>{variant.farbe}</option>
)}
</select>
<Flickity
flickityRef={c => (flkty = c)}
className={'carousel'}
elementType={'div'}
options={flickityOptions}
disableImagesLoaded={true}
reloadOnUpdate={true}
static={true}
>
{variants.map((galitem) =>
galitem.gallerie.map((galimg) =>
<div key={galimg.id} id={galimg.id.replace(/[^\w\s]/gi, '')}>
<span>{galimg.id}</span>
<Image fluid={galimg.localFile.childImageSharp.fluid} />
</div>
)
)}
</Flickity>
</>
)
}
Any ideas or pointers would be much appreciated :)
Switched from a dropdown to buttons just to simplify the whole thing and see where it goes wrong. Seems like flickity only accepts the value directly but not from state or any other variable.
This works:
const selectSlide = (e) => {
flkty.selectCell( `.${e.target.getAttribute("data-selector")}` )
};
...
<button onClick={selectSlide} data-selector={variant.gallerie[0].id} key={variant.farbe}>{variant.farbe}</button>
If anybody knows if this is a flickity thing or (more likely) I was doing something completely wrong I'd still appreciate some pointers so I know better next time :)
I implemented a multiple select dropdown from react-bootstrap documentation.
It does not let me do multiple select and only gets the last clicked option. I have state variable set to array. What else am I missing? App is created with create-react-app.
I have state set to array inside the class constructor. Binding of event handler also done in the constructor.
Next, I'm showing my event handler followed by form group with onChange and value set to state. (note I have a drop-down above this which is working fine.)
I then pass this value to a few classes before it's parsed to JSON. The last pastes are those classes. I have removed other parameters so easier to read, any ideas, feel free to ask for more info.
this.state = {
codeCoverage: [],
}
this.handleCodeCoverageChange = this.handleCodeCoverageChange.bind(this);
//Event handlers below
handleCodeCoverageChange(event){
this.setState({
codeCoverage: event.target.value
})
}
<Form.Group>
<Form.Label>Please choose your desired code coverage software(s)</Form.Label>
<Form.Control as="select" value={this.state.codeCoverage} onChange={this.handleCodeCoverageChange} multiple>
<option value="">--Please choose an option--</option>
<option value="cobertura">Cobertura</option>
<option value="sonarcube">Sonarcube</option>
</Form.Control>
</Form.Group>
var configurator = new Configurator(this.state.codeCoverage)
class Configurator
{
constructor(
code_coverage)
{
this.pipeline = new Pipeline(code_coverage)
}
}
class Pipeline
{
constructor(code_coverage)
{
this.analysisAndSecurity = new AnalysisAndSecurity(code_coverage)
}
class AnalysisAndSecurity{
parameter
constructor(code_coverage)
{
this.code_coverage = code_coverage
}
}
In your handleChange function you assign state.codeCoverage the value of the selected element instead of adding it to the array of selected element. This is why when you select another element it deletes the old value. I would recommend logging e.target.value and this.state.codeCoverage to better understand. As for the solution:
Since you are using multiple select it expects an array as value instead of a single value. So you need to change two things in your handleChange method.
First you need to add your element to existing values and not replace them.
You need to handle when a selected element is clicked again and needs to become unselected.
You can do both these tasks as shown below:
handleChange = e => {
const { codeCoverage } = this.state;
// Find the value selected the codeCoverage array
const index = codeCoverage.indexOf(e.target.value);
// If the value is not found then add it to the array
if (index === -1) codeCoverage.push(e.target.value);
// If value found then remove the value to unselect
else codeCoverage.splice(index, 1);
// Set the state so that the component reloads with the new value
this.setState({ codeCoverage: [...codeCoverage] });
};
TL;DR: calling this.setState to update an array avoid the change of a < select > label with the selected < option >
Explanations:
I am building an array of options like that (dd is for drop down):
let ddUsers = (this.state.users.map(user => <option key={uuid.v4()} value={user.key}>{user.name}</option>))
Then here is my select:
<select onChange={this.handleFilter}>{ddUsers}</select>
This works perfectly: it appears first with the first option and is able to display the others.
Here is my onChange function: handleFilter
handleFilter = (event) => {
let selectedUsers;
console.log(event.target.value, this.state.DEFAULT_KEY)
if (event.target.value === this.state.DEFAULT_KEY) {
selectedUsers = [...this.state.users];
} else {
selectedUsers = (this.state.users.filter(user => user.key === event.target.value));
}
// this.setState({selected: selectedUsers}); <-- The problem comes from this line (looks like atleast)
}
Now with the commented last line, the label of the select correctly update to the selected options. But when I uncomment this line, the function is called, this.state.selected is updated BUT the select is blocked on the 1st option.
Is that clear? What am I missing?
In fact: just calling setState in the function avoid the correct change
This is because you are setting the key of option to dynamic value which gets updated every time. Try to use a know and unique one as the key. Code such as below
<option key={user.key} value={user.key}>
This ensures, you have unique key and also is predictable.