Select text in react-select - reactjs

I'm able to focus a react-select programmatically, but I can't select the text in the input field.
I'm getting a reference to react-select like so:
const selectRef = useRef()
...
<Select
ref={selectRef}
// other props
</Select>
And then:
selectRef.current.focus()
selectRef.current.select()
Select focuses successfully, but for the second line (which I believe should work on an input element) I get:
TypeError: selectRef.current.select is not a function
How do I select text inside the react-select's input field?

react-select, I believe, handles this focus programmatically. The ref.current is not the search input.
We can achieve the expected behaviour using the same HTMLInputElement's select() method, however.
react-select provides a prop inputId whose value is attached directly to the input as an Id. onFocus we can select it.
import React, {Component} from "react";
import Select from "react-select";
function SingleSelect() {
const selectRef = useRef(null);
function handleButtonClick() {
selectRef.current.focus();
}
function handleFocus() {
document.querySelector("#select-input").select();
};
return (
<>
<Select
inputId = "select-input"
ref={selectRef}
onFocus = {handleFocus}
options = {[
{value: "1", label: "one"},
{value: "2", label: "two"},
]}
defaultInputValue = "some random string"
/>
<button onClick={handleButtonClick}>Focus!</button>
</>
);
}
Here is working example of the same code.
I hope this is what you wanted. Thanks :)

Just a couple of changes -:
<Select
ref={(n) => this.selectRef = n}
// other props
</Select>
And we can access the inputValue like this --> this.selectRef.select.props.inputValue
Working Fiddle -> https://stackblitz.com/edit/react-43utra

The reason your code doesn't work is because the ref to the Select component is not the same as the innerRef to the input. You would access it instead via the following object chain...
selectRef -> current (StateManager) -> select(Select Component) -> inputRef (Input Component)
selectRef.current.select.inputRef.select()

Related

React-Select: Page turns white after selecting an option

I am pretty new to React and am trying to use React-Select for a simple dropdown menu.
When you selected an option it should display the value under it, for that I'm using the onChange function and the useState Hook, but everytime I select something, the whole page just turns white.
App.js
import "./App.css";
import Select from "react-select";
import { useState } from "react";
function App() {
const [selected, setSelected] = useState(0);
const options = [
{ value: "1", label: "a" },
{ value: "2", label: "b" },
{ value: "3", label: "c" },
];
return (
<div>
<Select placeholder="Choose one"
defaultValue={selected}
onChange={setSelected}
options={options}
/>
<h1>{selected}</h1>
</div>
);
}
export default App;
Any help is appreciated, thank you.
I've done a simple edit on your code:
When onChange fires you set the selected value with onChange={(e)=>setSelected(e.value)}
Here's a functional codesandbox.
If you inspect your console in the browser, you'll see the issue is coming from the h1 component. This is probably what you're trying to achieve:
<h1>{selected.value}</h1>
I found a way to solve this, however I don't know if this is the best solution.
I created a function handleChange
const handleChange = e => {
setSelected(e.value);
}
and added a value prop, and called the function with onChange
<Select placeholder="Choose one"
defaultValue={selected}
value={options.find(obj => obj.value === selected)}
onChange={handleChange}
options={options}
/>
I don't know why
<h1>{selected.value}<h1>
doesn't work, since it's practically the same what I'm doing in handleChange, right?

How do I trigger the change event on a react-select component with react-testing-library?

Given that I can't test internals directly with react-testing-library, how would I go about testing a component that uses react-select? For instance, if I have a conditional render based on the value of the react-select, which doesn't render a traditional <select/>, can I still trigger the change?
import React, { useState } from "react";
import Select from "react-select";
const options = [
{ value: "First", label: "First" },
{ value: "Second", label: "Second" },
{ value: "Third", label: "Third" },
];
function TestApp() {
const [option, setOption] = useState(null);
return (
<div>
<label htmlFor="option-select">Select Option</label>
<Select
value={option}
options={options}
onChange={option => setOption(option)}
/>
{option && <div>{option.label}</div>}
</div>
);
}
export default TestApp;
I'm not even sure what I should query for. Is it the hidden input?
My team has a test utility in our project that lets us select an item easily after spending too much time trying to figure out how to do this properly. Sharing it here to hopefully help others.
This doesn't rely on any React Select internals or mocking but does require you to have set up a <label> which has a for linking to the React Select input. It uses the label to select a given choice value just like a user would on the real page.
const KEY_DOWN = 40
// Select an item from a React Select dropdown given a label and
// choice label you wish to pick.
export async function selectItem(
container: HTMLElement,
label: string,
choice: string
): Promise<void> {
// Focus and enable the dropdown of options.
fireEvent.focus(getByLabelText(container, label))
fireEvent.keyDown(getByLabelText(container, label), {
keyCode: KEY_DOWN,
})
// Wait for the dropdown of options to be drawn.
await findByText(container, choice)
// Select the item we care about.
fireEvent.click(getByText(container, choice))
// Wait for your choice to be set as the input value.
await findByDisplayValue(container, choice)
}
It can be used like this:
it('selects an item', async () => {
const { container } = render(<MyComponent/>)
await selectItem(container, 'My label', 'value')
})
You can try the following to get it working:
Fire focus event on the ReactSelect component .react-select input element.
Fire a mouseDown event on the .react-select__control element
Fire a click on the option element that you want to select
You can add a className and classNamePrefix props with the value of "react-select" in order to specifically select the component you are trying to test.
PS: In case you are still stuck I'd encourage you to take a look at this conversation from where the above answer is borrowed - https://spectrum.chat/react-testing-library/general/testing-react-select~5857bb70-b3b9-41a7-9991-83f782377581

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.

react-select: Do we need CSS as well just to display a normal select list?

I have following code which displays a normal select list. It is working fine logically and i am able to log when any change happens. But the problem is, very small box is being displayed instead of a text box which is adequate to show the options.And my placeholder is being displayed as a normal text. But when i click on placeholder, all values are being displayed.
import Select from 'react-select;
class SelectList extends Component {
onChange() {
console.log("Value Changed");
}
render() {
var Select = require('react-select');
var options = [
{ value: 'one', label: 'India' },
{ value: 'two', label: 'Singapore' },
{ value: 'three', label: 'IceLand' }
];
return(
<Select
name="Select List"
value="one"
options={options}
onChange={this.onChange.this(bind}}
/>
);
}
do I need any CSS things here. Could anybody let me know what am I missing here?
[1]: https://i.stack.imgur.com/NA4hT.png
You're already importing Select at the top, why are you doing
var Select = require('react-select');
again in the render function?
Also your onChange handler should be onChange={this.onChange} and you can bind the handler at the top in the constructor like this:
this.onChange = this.onChange.bind(this);
Or you can pass it to the Select component like this
onChange={() => {this.onChange()}}
As for the CSS issue, from github:
// Be sure to include styles at some point, probably during your bootstrapping
import 'react-select/dist/react-select.css';

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

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}
>

Resources