Add custom data- attribute to Option component react-select - reactjs

I am creating a test for my dropdown searchable component using react-select library.
I am not able to add data-testid attribute to the Option component when customizing that component as it is defined in react-select documentation.
The data-testid attribute does not show in the DOM for Option element.
Option component
import Select, { components } from 'react-select';
const CustomOption = (props: OptionProps<SearchDropdownOption, false>) => (
<components.Option {...props} data-testid="test-id" />
);
For example i had a success with the Input component for search version of the dropdown and data-testid attribute shows in the DOM:
Input component
import Select, { components } from 'react-select';
const CustomInput = (props: InputProps<SearchDropdownOption, false>) => (
<components.Input {...props} data-testid="test-id" />
);
And than using it in Select component:
<Select<SearchDropdownOption, false>
components={{
Input: CustomInput,
Option: CustomOption,
}}
isSearchable={isSearchable}
/>

It is impossible to add custom attribute data-testid directly to the Option component as i did for Input component. I need to extend this component with an HTML span element, or any other, and add this attribute to that element directly:
const CustomOption = (props: OptionProps<SearchDropdownOption, false>) => {
return (
<components.Option {...props}>
<span data-testid="test-id" key={props.innerProps.key}>
{props.data.label}
</span>
</components.Option>
);
};
NOTE
This key prop is important as it gives the regular React uniqueness to the element and value for that can be used from the react-select's innerProps property:
key={props.innerProps.key}

const OptionWrapper = (customProps: IOptionProps) =>
(nativeProps: OptionProps) => (
<components.Option
{...nativeProps}
innerProps={{
...nativeProps.innerProps,
...customProps
}}
/>
);
Option = OptionWrapper({ 'data-testid':"test-id" })
Try this

Related

Defining Components in React

I am creating a react website in which many pages have a specific button that should look the same for all. Should this button be its own component? If so, how would I specify the onClick events to be different for each button if it is a component?
Yes, it should be its own component.
Create it in a separate file so you can import them where you need to use.
The component should receive a onClick prop that you pass to the internal button tag.
See this example: https://reactjs.org/docs/components-and-props.html#composing-components
export const Button = ({ label, onClick, disabled }) => {
return (
<button
onClick={onClick}
disabled={disabled}
>
{label}
</button>
)
}
and then you can export this component inside any of the components you want, and pass in values like onClick and label to make it more dynamic
export const DemoFunction () => {
const onClickHandler = () => {}
return (
<Button label="Click" onClick={onClickHandler} />
)
}

click event is not triggering on input element from ref

I am using react 17.0.2 and material ui 4.11.4
I want to customize the select element appearnce (like Chip component of material ui). For this purpose I am using Autocoomplete component which renders an input element. I have rendered Chip component below the input element.
I am also getting the ref in the renderInput callback function which I am ustlising to trigger input click from Chip component.
When I log the params.inputProps.ref.current I am indeed getting the input element but calling click function does not show the dropdown but just focus the input element. When I click the input directly then it shows the dropdown.
I have created a sandbox of this behaviour.
CodeSandBox
I would recommend using state as a controlled component.
export default function App() {
const classes = useStyles();
const statusDropdownInput = useRef(null);
let [val, setVal] = useState(false) // <- Store the open state
const handleStatusDropdownClick = (params,e) => {
setVal(!val) // <- Will toggle the dropdown
};
let inputElement = null
return (
<Autocomplete
className={classes.statusDropdown}
id="status"
open={val} // <- Control the input elements state here
options={["Option 1", "Option 2"]}
renderInput={(params) => (
<div ref={params.InputProps.ref}>
<input type="text" {...params.inputProps} />
<Chip
size="small"
avatar={<DoneOutlinedIcon />}
label="Published"
clickable
onDelete={() => {
console.log(params.inputProps);
}}
onClick={(e) => handleStatusDropdownClick(params)}
deleteIcon={<ExpandMoreIcon />}
/>
</div>
)}
/>
);
}

Dynamically add a prop to Reactjs material ui Select

I have a question about the material UI Select component and how to set props dynamically.
I'm trying to wrap the material UI Select (https://material-ui.com/components/selects/) component in my CompanySelect so I can add some additional styling and other stuff.
Main question
How can I dynamically add/remove the disableUnderline prop on the material UI Select component.
When I set disableUnderline = null and variant = 'outlined' I get a warning that disableUnderline is an unknown prop. when using variant = 'standard' there is no warning.
CompanySelect component code
import React from 'react';
import Select from '#material-ui/core/Select';
import PropTypes from 'prop-types';
import ExpandMoreRoundedIcon from '#material-ui/icons/ExpandMoreRounded';
import './style.scss';
const CompanySelect= (props) => {
const {
variant,
disableUnderline,
children,
...
} = props;
return (
<Select
disableUnderline={disableUnderline}
variant={variant}
...
>
{children}
</Select>
);
};
CompanySelect.propTypes = {
variant: PropTypes.oneOf(['outlined', 'filled', 'standard']),
disableUnderline: PropTypes.bool,
children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]).isRequired
};
CompanySelect.defaultProps = {
variant: 'standard',
disableUnderline: null,
};
export default CompanySelect;
Standard usage
<AtriasSelect variant="standard" disableUnderline>
<MenuItem />
<MenuItem />
</AtriasSelect>
Outlined usage
<AtriasSelect variant="outlined">
<MenuItem />
<MenuItem />
</AtriasSelect>
The standard usage works. With the disableUnderline the default underline is removed as documented on the Input API page. (https://material-ui.com/api/input/).
Problem occurs when I use the outlined variant because then the Select inherits the OutlinedInput API. If you look at the OutlinedInput API (https://material-ui.com/api/outlined-input/) then you can see it does not have the disableUnderline prop.
I gave the disableUnderline prop the default value 'null' assuming it would not render when not supplied. But when using the Outlined variant (without disableUnderline prop) I get the following warning.
React does not recognize the `disableUnderline` prop on a DOM element. If you intentionally want it to appear in the DOM as a custom attribute, spell it as lowercase `disableunderline` instead. If you accidentally passed it from a parent component, remove it from the DOM element.
So my question, is there a way to not add the prop at all. Something like the following pseudo code:
return (
<Select
{variant !== 'outlined' ? disableUnderline : null} //Pseudo code, just to show what I need
variant={variant}
...
>
{children}
</Select>
);
Possible solution
The only solution I see now (my react knowledge is limited) is adding an if statement in the CompanySelect component that will check if the outlined variant is used or not. But this means I need to have a lot of duplicate code in the CompanySelect code.
const CompanySelect= (props) => {
const {
variant,
disableUnderline,
children,
...
} = props;
if (variant !== 'outlined'){
return (<Select disableUnderline={disableUnderline} variant={variant} ...> {children} </Select>);
} else {
return (<Select variant={variant} ...> {children} </Select>);
}
};
Is there maybe another way of solving this problem?
You can use spread operator (...) in returned JSX like this:
const CompanySelect= (props) => {
const {
variant,
disableUnderline,
children,
...
} = props;
return (
<Select
variant={variant}
{...(variant !== "outlined" && { disableUnderline: true })}
>
{children}
</Select>
);
};
I think the proper way is to use React.cloneElement
Something like
let props = {
variant: variant,
};
// Your dynamic props
if(variant !== 'outlined') {
props[disableUnderline] = 'your value';
}
<div>
{
React.cloneElement(
Select,
props
)
}
</div>

Restrict input to positive numbers in react-select's Creatable

I am trying to use react-select's Creatable for an input field of type number. Code below.
import React from 'react';
import Tooltip from '#atlaskit/tooltip';
import Creatable from "react-select/creatable";
import { components } from 'react-select';
const Input = props => {
if (props.isHidden) {
return <components.Input {...props} />;
}
return (
<div>
<Tooltip content={'Custom Input'}>
<components.Input {...props} type="number"/>
</Tooltip>
</div>
);
};
const handleInputChange = (value,action) => {
console.log(value);
console.log(action);
}
export default () => (
<Creatable
closeMenuOnSelect={false}
components={{ Input }}
backspaceRemovesValue
isClearable
onInputChange={handleInputChange}
/>
);
When I type a - or e the onInputChange is not trigerred and the value of the Creatable is not being set. Also if I try to remove the symbols using a backspace it does not work either. Any idea on how to restrict the input to positive numbers only?
Here is a codesandbox example to see what is happening.
The react-select library provides us with a onKeyDown prop that will pass on the event to the callback function.So, all I had to do was use this prop and then prevent default if the user enters a character that I am not expecting them to enter.
Codesandbox example

Access inputValue in <Option /> component in react-select 2.*

I'm trying to highlight (underline or bold) the text that was entered within all my displayed options. This was quite straightforward in react-select 1.* but I fail in 2.*. when trying to access the inputValue within the custom component.
This code snippet illustrates my attempt when inputValue would be available as in props:
import React from 'react'
import match from 'autosuggest-highlight/match/index'
import parse from 'autosuggest-highlight/parse/index'
export default props => {
// Extract matching parts from the inputValue (value typed into text field)
const matches = match(props.label, props.inputValue)
const parts = parse(props.label, matches)
return (
<div>
{parts.map((part, index) => {
return !part.highlight ? (
<span>{part.text}</span>
) : (
<strong>{part.text}</strong>
)
})}
</div>
)
}
If you were using a custom Option component then you could access the inputValue from within the props.selectProps.
const Option = props => {
console.log('props', props);
const { innerProps, innerRef, selectProps, data } = props;
return (
<div ref={innerRef} {...innerProps}>
// generate your highlighted Option from data.label here, using
// selectProps.inputValue
</div>
);
};
// ...
<Select {...otherProps} components={{Option}} />

Resources