Can't get the target attributes of material-ui select react component - reactjs

I'm trying to get the id,name, and value from Select field element of Material-UI react component.
This is my container:
//some code is removed to keep things simple
class MyContainer extends Component {
constructor(props) {
super(props);
}
render() {
return(
<MyComp onChange={this._onChange.bind(this)} />
);
}
_onChange(evt) {
console.log(evt.target);
console.log(evt.target.id); //blank
console.log(evt.target.name); //undefined
console.log(evt.target.value); //html value of selected option!
}
}
export default connect(select)(MyContainer);
in my presentational component:
import React, {Component} from 'react';
import Select from 'material-ui/SelectField';
import MenuItem from 'material-ui/MenuItem';
const someData = ["aaaa", "bbbb", "ccccc"];
class MyComp extends Component {
render() {
const {onChange} = this.props;
return (
<form >
<Select
value={this.props.test}
name={"test"}
id={"test"}
onChange={this.props.onChange}
hintText={"Select a fitch rating service"}>
{
someData.map((e) => {
return <MenuItem key={Math.random()} value={e} primaryText={e}/>
})
}
</Select>
</form>
);
}
}
Problem is that _onChange(evt) is giving this values:
evt.id is blank
evt.name is undefined
evt.target.value is <div>whatever the selected value was</div>
It seems as though the passed argument to _onChange(evt) is not the SELECT but rather the option since when i print out evt.target it gives <div>whatever the selected value was</div>. anyone knows why? If i use a plain select field (not material-ui) then this works as expected (i.e. i can get the id,name, correct value of selected option). How do i get the target id, name..etc from onChange event of Material-UI Select component?
P.S i'm using the same _onChange method for TextField component of material-ui and it works there. I've also tried:
_onChange = (event, index, value) => {
console.log(event.target.id); //blank
console.log(event.target.name); //undefined
console.log(index); //correct index
console.log(value); //correct value
};

I'd keep it simpler using event.currentTarget instead of event.target.
In your event handler, you can access the attribute of interest from the element that triggered the event by:
_onChange(evt) {
console.log(evt.currentTarget.getAttribute("data-name");
console.log(evt.currentTarget.getAttribute("data-value");
}
thus calling .getAttribute on the attribute name prepended by "data-"

Update 2
In response to your comments:
As per the material-ui docs, getting back the touchtap event on option element rather than the select element is expected. If you want the id and name of the element, I would suggest binding the variables to the callback:
The onchange method in the parent component:
_onChange(id, name, evt, key, payload) {
console.log(id); //id of select
console.log(name); //name of name
console.log(payload); //value of selected option
}
And when you attach it to the select component, you need to use bind
<Select
value={this.props.test}
name={"test"}
id={"test"}
onChange={this.props.onChange.bind(null,"id","name")}
hintText={"Select a fitch rating service"}>
Update
Here are the react event docs. Under the event-pooling you will find reference to the use of e.persists() in the block quote. The explanation given in this issue is that React pools the event object, so it get's reused when another event is fired.
React has a rather special event object. It wraps the native event in a synthetic event, which is what event points to in your _onChange method.
The problem is that this synthetic event object is recycled and reused for the next event, so it is garbage collected and set to null. I don't think this is well documented, but I'll search for a link and update this answer once I find it.
The solution is for the first line in your event handler to make a copy of the event, persist it, or get a reference to the native event:
Make a copy of the event
_onChange = (event, index, value) => {
e = _.cloneDeep(event); // Here I'm using the cloneDeep method provided by underscore / lodash . User whatever library you prefer.
console.log(e.target.id);
console.log(e.target.name);
};
Persist the event
_onChange = (event, index, value) => {
event.persist() // This stops react from garbage collecting the event object. It may impact performance, but I doubt by much.
console.log(event.target.id);
console.log(event.target.name);
};
Get a reference to the native event object
_onChange = (event, index, value) => {
e = event.native; // This looks the same as a vanilla JS event
console.log(e.target.id);
console.log(e.target.name);
};

This is an answer for latest version of Material UI and React (2020):
You need to have the name property on your <Select> element. That name value should correspond to the field's name in your object.
This should work (name is set to the field you're trying to update):
<Select
labelId="demo-simple-select-outlined-label"
id="demo-simple-select-outlined"
name="taskType"
value={props.job.taskType || ""}
label="{props.label}"
onChange={props.onTaskTypeChange}
>
But this will not work (name is omitted). This will return evt.target.name = undefined:
<Select
labelId="demo-simple-select-outlined-label"
id="demo-simple-select-outlined"
value={props.job.taskType || ""}
label="{props.label}"
onChange={props.onTaskTypeChange}
>

Related

antd 4: setting a default value for Select with defaultValue, can't use initialValue

In our rather complex form we have a dynamic form field (similar to the example in the antd documentation, except using a Select field). We use initialValue to feed our Form data from the database, now we want to have our Select fields, which are added dynamically, to have a default value.
The problem exists in the fact that it isn't possible to add an initialValue to fields that haven't rendered yet + form doesn't know how many dynamic Select fields will be added.
So instinctively I resorted to the defaultValue prop on the Select box, which in my eyes should just work, but it doesn't. (in antd 4 there is no fieldDescriptor with a defaultValue)
Maybe this example will explain better what I'm trying to say:
https://codesandbox.io/s/thirsty-hamilton-m7bmc
If you add a field in the example and hit submit, it will complain the field is required. However it certainly does have a value in there, but apparently not for the Form state.
I hope someone else has encountered a similar issue
Your Select components need VALUE to be defined. I see you have defaultValue, but they also need such properties as VALUE, and ONCHANGE. When you add them the select will start to work properly.
1). It is better to define default value inside VALUE property, so that if nothing is selected by user (selected value is undefined), the value will default to whatever you mention in the following condition with ternary operator:
value={selectedValue ? selectedValue : defaultValue}.
2). In onChange property you need to pass value and update the state with that value:
onChange={(value) => this.updateSelectedValue(value)}
import React, { PureComponent } from "react";
import { Form, Select } from "antd";
const { Option } = Select;
export default class DynamicFieldSet extends PureComponent {
state = {
selectedValue: undefined,
}
updateSelectedValue = value => this.setState({ selectedValue: value })
render() {
const { selectedValue } = this.state;
return (
<Form>
<Form.Item>
<Select
value={selectedValue ? selectedValue : "TAG"}
onChange={(value) => this.updateSelectedValue(value)}
>
<Option value="MARKER">Marker</Option>
<Option value="TAG">Tag</Option>
<Option value="FIELD">Field</Option>
</Select>
</Form.Item>
</Form>
);
}
}

Office ui Fabric People Picker, pass varaibles to onChangeEvent

I would like to know if there is a possibility to added a variable to the default onchange event of the office ui fabric react People Picker component.
In the onchange event default is onChange?: (items?: IPersonaProps[]) => void
I would like to pass an variable to add to an array like Key Value for using later in my code.
You can build a HOC to achieve this.
Define
// Your HOC component
interface Props {
yourCustomState: string // get custom value from props
...
return (
<>
<Select
onChange={(e: any) => onChange(e, yourCustomState)} // add custom value to callBack function
...
Usage
handleOnChangeEvent = () => (event, yourCustomStateValue: string) => {
console.log(yourCustomStateValue);
... // event
}
<YourComponent
yourCustomState={value}
onChange={this.handleOnChangeEvent()}
...

How to handle a different type of event with a handler, specifically a dropdown and not a textfield. SPFX/React

This is a continuation on from:
How to create a handler function which 'handles' multiple textfields but also character counter textfields as well
I now want to use this handler to 'handle' a drop down along with textfields. I have this so far:
public handleChange = (evt: any) => {
const {value} = (evt.target as any);
const obj = {
[evt.target.name]: value,
};
this.setState(prevState => ({prevState, ...obj}));
}
Which works for textfields.
I now want it to handle a Dropdown component from office-ui-fabric. I've tried creating a completely different handler like this:
public onDropDownChange = (evt: any) => {
const {value} = (evt.target as any);
this.setState({ value: evt.target.value});
}
The above, experimental, onDropDownChange does not work. It gives an error saying value does not exist on event. I'm fairly sure this is because whatever is returned from a drop down is not 'value' and could quite well be 'item'.
I could write a function specifically for this drop down but I'm trying to cut down the amount of code written using handlers.
JSX:
<Dropdown
selectedKey={this.state.SelectedJobPlanDD}
placeholder="Select..."
onChange={this.onDropDownChange}
options={jobPlanDDOptions}
defaultSelectedKey={this.state.JobPlanDD}
/>
State:
SelectedJobPlanDD: undefined,
JobPlanDD: null,
BTW: If I log the change of the drop down I get undefined, so that's the start of the problem.
Also, here is a 'standalone' function that I've used in the past for drop downs, it may help:
private _dropDownFunction = (item: IDropdownOption) => {
this.setState({ SelectedItem: (item && item.key) ? item.key.toString() : null })
Any idea what I could use? My understanding of event handlers and ultimately basic JS logic is limited, but I learn a great deal from any advice given on SO.
UPDATE: From LevelGlow's answer I've been able to return the selected item from the drop down. I'm using this for the handler:
public onDropDownChange = (evt: any, item) => {
// const {item} = (evt.target as any);
console.log(evt+'evt with text');
console.log(evt); // THIS IS THE ONE THAT SHOWS THE SELECTED ITEM!
console.log(item.text+'item.text with text'); //Shows undefined
console.log(item); //shows 3 (I guess this is id of the item)
console.log(item.key+'item.key with text'); //shows undefined.
this.setState({
});
}
Now I'm not sure how I'd implement this into the handler to update the state item.
To obtain the selected item from the Dropdown, structure the Dropdown's onChange handler as the docs say, example below:
public onDropDownChange = (evt, item) => {
console.log(item.key);
console.log(item.text);
};
The onChange event here has a DOM object as target which stores only the text of the dropdown and it can be accessed by
evt.target.querySelector('span').innerText;
You can add the id prop to your Dropdown:
<Dropdown
id="JobPlanDD"
...
/>
Now the component generates a new id for you in the previously mentioned span tag and you can access it by calling
evt.target.querySelector('span').id; which returns "JobPlanDD-option"
And we will be using it as key for the state.
So your selectedKey should be:
selectedKey={(this.state["JobPlanDD-option"]) ? (this.state["JobPlanDD-option"]).key : undefined}
What this line doing is: do we have JobPlanDD-option in out state? if yes, we'll pass the JobPlanDD-option item's key to selectedKey, if we don't, we'll pass undefined.
Structure your shared handler like this:
public handleChange = (evt: any, item): void => {
if(evt.target.value){
//handle textfields here
} else {
//handle dropdowns here
const id = evt.target.querySelector('span').id; //get the span's id
//update state with the generated id and the selected option
this.setState({ [id] : item });
}
}
Since we updated the shared handler, we should update also how TextFields are calling it:
<TextField
onChange={(e) => this.handleChange(e, null)}
...
/>
Now both text fields and dropdowns should work with this shared handler.

How to simulate selecting from dropdown in Jest / enzyme testing?

I'm trying to write jest tests for my React component that has a dropdown like this:
<select id="dropdown" onChange={this.handlechange} ref={this.refDropdown}>
{this.props.items.map(item => {
return (
<option key={item.id} value={item.id}>
{item.name}
</option>
);
})}
</select>
and the handler looks like this:
handlechange = () => {
const sel = this.refDropdown.current;
const value = sel.options[sel.selectedIndex].value;
//...
}
I want to simulate a user selecting the 2nd entry (or anything other than the first) in the list but am having trouble. If I simulate a "change" event it does fire the call to handlechange() but selectedIndex is always set to zero.
I tried this code in the jest test but it doesn't cause selectedIndex to be accessible in the handler.
const component = mount(<MyComponent/>);
component.find("#dropdown").simulate("change", {
target: { value: "item1", selectedIndex: 1 }
});
What happens is almost correct. If I look at the incoming event, I can see e.value is set to "item1" as I set, but it doesn't act like the selection was actually made.
I've also tried trying to send "click" simulations to the Option element directly but that does nothing.
What's the right way to simulate a selection from a dropdown?
Try this approach:
wrapper.find('option').at(0).instance().selected = false;
wrapper.find('option').at(1).instance().selected = true;
You can trigger a change event since you have your this.handlechange trigger onChange:
const component = mount(<MyComponent/>);
component.find('#dropdown').at(0).simulate('change', {
target: { value: 'item1', name: 'item1' }
});
I would say you have to add .at(0) because Enzyme will find a list of values even if you only have one element with that ID.
Try changing the html "onInput" to "onChange" because you are simulating the "change" event in jest.
Short Answer -
Use the following snippet
import userEvent from "#testing-library/user-event";
userEvent.selectOptions(screen.getByTestId("select-element-test-id"), ["option1"]);
Detailed Answer -
.tsx file
.
.
<Form.Select
aria-label="Select a value from the select dropdown"
required
onChange={(e) => {
console.log("Option selected from the dropdown list", e.target.value);
optionChangedHandler(e.target.value);
}}
data-testid="select-element-test-id"
>
...CODE FOR RENDERING THE LIST OF OPTIONS...
</Form.Select>
.
.
.test.tsx file
import userEvent from "#testing-library/user-event";
it("Check entire flow", async () => {
render(
<YourComponent/>
);
// CHECK IF SELECT DROPDOWN EXISTS
const selectDropdown = await waitFor(
() => screen.getByTestId("select-element-test-id"),
{
timeout: 3000,
}
);
expect(selectDropdown ).toBeInTheDocument();
//"option2" is the element in the select dropdown list
userEvent.selectOptions(screen.getByTestId("select-element-test-id"), [
"option2",
]);
}
The above code will trigger the onChange function of the select element.

how to use semantic UI React Checkbox toggle?

I am trying to use Semantic UI react for layout. These are the following sample code I am having trouble with onChange. I can check to click the toggle but resets everytime I refresh.
import {
Checkbox
} from 'semantic-ui-react'
onChangeInput(event) {
let name = event.target.name
let value = event.target.value
let talent = this.state.newTalentProfile
talent[name] = value
this.setState({
newTalentProfile: talent
})
}
<Select
name = "willing_to_relocate"
ref = "willing_to_relocate"
defaultValue = {this.props.talent.willing_to_relocate}
onChange = { this.onChangeInput.bind(this)} >
<Option value = ""label = "" / >
<Option value = "YES"label = "YES" / >
<Option value = "NO"label = "NO" / >
</Select>
the below code doesn't work, but the above one works when i make changes it saves it to database
<Checkbox toggle
name = "willing"
ref = "willing"
label = "Willin To Relocate"
onChange = {this.onChangeInput.bind(this)
}
/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
onChangeCheckbox = (evt, data) => {
let checked = data.checked
console.log(checked)
}
<Checkbox toggle label='Running discounted price?'
onClick={(evt, data)=>this.onChangeCheckbox(evt, data)}
/>
This should do the trick.
Your code suggests you expect event.target to be a checkbox element, but it is not. If you examine the variable event.target within your browser, you'll notice that it points to a label element, not the checkbox itself. Therefore, I'm guessing that your event.target.value and event.target.label are evaluating to null, causing the rest of your code to not function the way you expect.
There's many ways to get around this. One way is to set a state variable for your component to represent the state of the checkbox:
class TheComponentThatContainsMyCheckbox extends Component {
state = {
textValue: null,
willingToRelocate: true
}
...
Then, you can create a handler that toggles this state when checked:
toggleCheckBox = () => {
const willingToRelocate = !(this.state.willingToRelocate);
this.setState({willingToRelocate});
// Note: if you have other logic here that depends on your newly updated state, you should add in a callback function to setState, since setState is asynchronous
}
Notice that I'm using arrow syntax here, which will automatically bind this for me. Then, hook it up to the onChange event of the Semantic UI Checkbox:
<Checkbox label='Willing to Relocate.' onChange={this.toggleCheckBox}/>
You can now use your this.willingToRelocate state to decide other application logic, passing it up or down using whatever state management pattern you like (state container/store like Redux, React Context, etc.).
I assume your this.onChangeInput handles database updates.
semantic ui react offers a prop checked for Checkbox.
You should have a state {checked: false} in your code, then this.setState whatever in componentDidMount after retrieving the database record.
Pass this.state.checked into checked prop of Checkbox.

Resources