Focus Select element - reactjs

I'm trying to programatically focus a select element in Material-UI in a useEffect block.
Trying to pass a ref to the component (ref={my ref}) itself does nothing, and trying to pass any variety of inputProps={{ ref: myRef }}, inputProps={{ inputRef: myRef }} throws an error for displayNode being undefined when I call focus() on the ref.
I am sure there is something obvious I am missing, what would be the correct syntax/way to do this?
const MyCard = ({
disabled,
}) => {
const inputField = React.useRef(null);
React.useEffect(() => {
if (!disabled && inputField.current) {
inputField.current.focus();
}
}, [inputField, disabled]);
return (
<Select
disabled={disabled}
inputProps={{ ref: inputField }}
required
id="answer"
name="answer"
autoComplete='off'
autoFocus
>
<MenuItem value="option1">option1</MenuItem>
<MenuItem value="option2">option2</MenuItem>
</Select>
)
}
Additional info from OP:
I need to be able to programmatically focus the select. Autofocus
works for me on the initial render, but not subsequently, specifically
I am trying to refocus the input after removing the disabled prop.

Make sure you check that it is defined before calling focus:
useEffect(() => {
if (myRef.current) {
myRef.current.focus()
}
}, [myRef])

Related

Passing value to hidden input from dropdown menu in react

I have react-select dropdown menu and hidden input which I pass to form when submiting...
using useState hook I created variable which tracks changes to react-select dropdown menu.
Hidden input has this variable as value also. I thought this would be enough.
But when I submit the form, console. log shows me that value of input is empty despite that variable that was selected from dropdown menu is actually updated.
I mean variable that I have chosen console logs some value, but hidden input thinks that it is still empty.
Does it means I have to rerender manually page each time I change that variable so input gets it's new value using useEffect ? Which is bad solution for me, I don't like it, thought it would be done automatically.
Or instead of useState I must create and use variable via Redux ? Which I also don't like, use redux for such small thing fills overcomplicated.
Isn't there any nice elegant solution ? :)
import { useForm } from 'react-hook-form';
const [someVar,setSomeVar]=useState('');
const {
register,
handleSubmit,
formState: { errors },
} = useForm({ mode: 'onBlur' });
const handleFormSubmit = (data) => {
console.error('success');
};
const handleErrors = (errors) => {
console.error(errors);
console.log(document.getElementsByName('hiddenInput')[0].value);
};
const options = {
hiddenInput: {
required: t('hiddenInput is required'),
},
};
.......
<form onSubmit={handleSubmit(handleFormSubmit, handleErrors)}>
<Select
options='...some options list'
onChange={(value) => setSomeVar(value)}
/>
<input
name='hiddenInput'
value={someVar}
{...register('hiddenInput', options.hiddenInput)}
/>
<button>submit</button>
</form>
UPDATED
Its because getElementsByName returns an array of elements.
You probably want
document.getElementsByName('hiddenInput')[0].value
I should add that really you should use a ref attached to the input and not access it via the base DOM API.
const hiddenRef = useRef(null)
// ...
cosnt handleSubmit =(e)=>{
console.log(hiddenRef.current.value);
}
// ...
<input
name='hiddenInput'
value={someVar}
ref={hiddenRef}
/>
However as you are using react-hook-form you need to be interacting with its state store so the library knows the value.
const {
register,
handleSubmit,
formState: { errors },
setValue
} = useForm({ mode: 'onBlur' });
// ...
<form onSubmit={handleSubmit(handleFormSubmit, handleErrors)}>
<Select
options='...some options list'
onChange={(value) => setValue('hiddenInput', value)}
/>
<input
name='hiddenInput'
{...register('hiddenInput', options.hiddenInput)}
/>
<button>submit</button>
</form>
You can remove const [someVar,setSomeVar]=useState('');
However, this hidden input is not really necessary as you mention in comments. You just need to bind the dropdown to react hook form.
// controller is imported from react hook form
<form onSubmit={handleSubmit(handleFormSubmit, handleErrors)}>
<Controller
control={control} // THIS IS FROM useForm return
name="yourDropdown"
rules={{required: true}}
render={({
field: { onChange, value, name, ref }
}) => (
<Select
options={options}
inputRef={ref}
value={options.find(c => c.value === value)}
onChange={val => onChange(val.value)}
/>
)}
/>
<button>submit</button>
</form>

React Material UI TextField type=number onStepChange analog?

I building a React component using Mui TextField. Its a number field so there are two ways to update the TextField component: Editing the text itself and using the step arrows. When "blurring" the field, I want to snap the value to the nearest multiple of 5. I also set the step="5" as well.
The component itself has an onChange event that passes the number value but I only want this event to fire when the user "steps" the value or the user "blurs" the field. Here's what I've got so far:
interface WeightFieldProps {
name?: string
placeholder?: string
label?: string
value?: number
onChange?: (value: number) => void
}
const WeightField: React.FC<WeightFieldProps> = ({
name,
placeholder,
label,
value = 0,
onChange
}) => {
const [weight, setWeight] = useState(value)
// Update the component but do not fire the parent onChange event.
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const value = parseInt(event.target.value)
setWeight(value)
}
// Update the component as usual but notify the parent component that
// the value has changed.
const handleBlur = (event: React.FocusEvent<HTMLInputElement>) => {
const value = snapWeightValue(parseInt(event.target.value))
setWeight(value)
if (onChange) onChange(value)
}
// Ideally, I want the parent component only be notified when the user
// is changing the input value via the step arrows.
// const handleStepChange = (event: React.SomeEvent) => {
// const value = parseInt(event.target.value)
// setWeight(value)
// if (onChange) onChange(value)
// }
return <TextField
name={name}
type="number"
placeholder={placeholder}
label={label}
InputProps={{
endAdornment: <InputAdornment position="end">lbs</InputAdornment>
}}
inputProps={{
step: "5",
style: {
fontSize: "2em",
padding: "10px",
textAlign: "right"
}
}}
value={weight}
onChange={handleChange}
onBlur={handleBlur}
// onStepChange={handleStepChange}
fullWidth
/>
}
What I'm hoping for is some kind of event handler specifically for when a user updates the field via the numeric step buttons or maybe a way to determine how the field is being updated during the onChange event so I can fire the component's onChange event only when the user is stepping the value?
I would suggest hiding the native arrow buttons, adding custom arrow buttons like in number inputs from UI-libraries, and then adding to these custom arrow buttons whenever handler you like. There is no such event that would be only triggered by clicking native arrow buttons.
you should move inputProps inside InputProps. else you will have eslint warning, no duplicate props allowed.
You can use onInput event handler inside inputProps as shown in below code.
ref: https://www.w3schools.com/jsref/event_oninput.asp
<TextField
name={name}
type="number"
placeholder={placeholder}
label={label}
InputProps={{
endAdornment: <InputAdornment position="end">lbs</InputAdornment>,
inputProps:{
step: "5",
onInput:()=> console.log('input changed'),
style: {
fontSize: "2em",
padding: "10px",
textAlign: "right"
}
}
}}
value={weight}
onChange={handleChange}
onBlur={handleBlur}
// onStepChange={handleStepChange}
fullWidth
/>

react-testing-library for material ui Text input

My Text Input is:
<TextField
className={classes.textField}
data-testid={name}
variant="outlined"
error={false}
required
onChange={(element) => {
if (onTextChange) {
onTextChange(name, element.target.value);
}
}}
disabled={!editEnable}
name={name}
label={label}
defaultValue={values}
fullWidth
/>;
and UI:
How to change the value of this text element in the React testing library?
In my case it works like this
it('should render input ', () => {
const field = screen.getByTestId('search-text-field').querySelector('input')
expect(field ).toBeInTheDocument()
fireEvent.change(field , {target: { value: 'google it'}});
expect(field.value).toBe('google it');
});
I don't think getting the input by the display value is a good idea as if that changes the whole test will fail. Instead you should get the label of the input field.
screen.getByLabelText(/^label/i)
Update
Just realised that my way only works if you include an id to the TextField and the ID must match the name. This does seem to be the preferred way to get the input via Material UI as you don't need to include a test-id or by the value.
<TextField
name={name}
id={name}
label={label}
{...otherProps}
/>
I often struggle to get Material UI and react-testing-library working. But if you know your "recipes" it's always the same.
Here is an example of an TextField
import * as React from 'react';
import { render, fireEvent } from '#testing-library/react';
import { TextField } from '#material-ui/core';
const inputMock = jest.fn();
const Test = () => (
<TextField
data-testid={name}
variant="outlined"
error={false}
required
onChange={inputMock}
name={name}
label={'label'}
defaultValue={'4711'}
placeholder={'Enter Number'}
fullWidth
/>
);
test('Input', () => {
const container = render(<Test />);
const input = container.getByDisplayValue('4711') as HTMLInputElement;
fireEvent.change(input, { target: { value: '42' } });
expect(input.value).toBe('42');
expect(inputMock.mock.calls).toHaveLength(1);
});
Here are some advises which selectors to use. So you can try a "better" one.
https://testing-library.com/docs/guide-which-query
Cheers
Thomas

Material-ui autocomplete clear value

I have one problem in my react code.
I use Material-ui and redux-form. I have select input like and after change this select i should reset value in . I use action 'change' from react-form and set value for textfield. But label in still remains. Can i clear or reset value in ?
<Autocomplete
options={list}
getOptionLabel={option => option.name}
onInputChange={onChange}
onChange={onChangeAutoComplete}
noOptionsText='Нет доступных вариантов'
loadingText='Загрузка...'
openText='Открыть'
renderInput={params => (
<Field
{...params}
label={label}
name={fieldName}
variant="outlined"
fullWidth
component={renderTextField}
className={classes.textField}
margin="normal"
/>
)}
/>
Using hooks on the value prop breaks the functionality of the autocomplete component ( at least for me ). Using class, and setting the local state is the same.
Luckily it is a react component, so it have a "key" prop. When the key prop changes, the component is re-rendered with the default values ( which is an empty array since nothing is selected). I used hooks in the parent component and passed the values to the key prop, whenever reset is needed.
<Autocomplete
key={somethingMeaningful} // Bool, or whatever just change it to re-render the component
//...other props
/>
Hope this helps!
Material UI Autocomplete onInputChange callback provides reason argument. If input has been changed by input, reason will be input and if you selected option then reason will be reset.
onInputChange={(event, newInputValue, reason) => {
if (reason === 'reset') {
setValue('')
return
} else {
setValue(newInputValue)
}
}}
setValue is useState and you can pass value state to autocomplete value property.
use value in your <Autocomplete /> like this:
<Autocomplete
value={this.state.value} //insert your state key here
//...other props
/>
Then clear state of that key, to clear the autocomplete field value
I am going to post a very dirty way of clearing the value of Autocomplete. Try it ONLY when nothing else works;
import React, { useRef } from 'react';
...
const autoC = useRef(null);
...
<Autocomplete
...
ref={autoC}
/>
and then when you want to clear the value;
const ele = autoC.current.getElementsByClassName('MuiAutocomplete-clearIndicator')[0];
if (ele) ele.click();
This is what worked for me.
const [name, setName] = useState('');
<Autocomplete
inputValue={name}
onChange={(e,v)=>setName(v?.name||v)}
...
/>
<Button onClick={()=>setName('')}>
Clear
</Button>
You can use something like the following to clear the autocomplete field when an item is selected.
<Autocomplete
value={null}
blurOnSelect={true} />
Note that you may also need to set clearOnBlur={true} if you're using the freeSolo option.
Source https://mui.com/api/autocomplete/#props
I achieved this by updating the inputValue prop where multiple prop is false. If you are using multiple prop, then there is a propblem (bug). Selected values does not get erased.
When I encountered this, it was when options for the autocomplete changed, and wanted to clear the input value. It wouldn't clear with just the options changing. What worked for me is adding a key value onto the autocomplete which depended on the change which necessitated clearing.
To solve this, I created a hook that watches the value state of the autocomplete and set the value of the input if the checkClear returns true;
function useAutocompleteInputClear(watch, checkClear) {
const elmRef = useRef(null);
useMemo(() => {
if (!elmRef || !elmRef.current) return;
if (!checkClear || typeof checkClear !== "function") return;
const button = elmRef.current.querySelector("button")
if (checkClear(watch) && button) {
button.click();
}
}, [watch])
return elmRef;
}
Its first argument is the value that should be watched and its second argument is a function that returns a boolean. if it is true the clearing will happen.
Also, the hook returns a ref that needs to pass as ref prop to Autocomplete.
const elmRef = useAutocompleteInputClear(value, v => !v || !v.id)
<Autocomplete ref={elmRef}
value={value}
...
using onChange property we can clear the value by clicking the clear icon in the following way
<Autocomplete
fullWidth={true}
label={'Source'}
margin={'noraml'}
multiple={false}
name={'Source'}
getOptionSelected={useCallback((option, value) => option.value === value.value)}
ref={SourceRef}
value={formValues.Source === '' ? {label: ''} : {label: formValues.Source}}
options={SourceStatus}
onChange={useCallback((e, v) => {
if (typeof v === 'object' && v !== null) {
handleInputChange(e, v) // help to set the value
} else {
handleInputChange(e, {label: ''}) // help to reset the value
}
})}
/>
In my case for multiselect freeSolo onChange props 3rd argument reason solved my all issues.
AutocompleteChangeReason can be:
blur
clear
createOption
removeOption
selectOption
and 2nd arg of this props gives u already updated list of (multiselect) value/s.
onChange={(_event, newOptions, reason) => {
setOptions(
reason === 'clear' ? [] : [...newOptions.map((o) => Number(o))],
);
}}
If you need only the selected value, set the value to an empty object and render the option to your needs.
<Autocomplete
value={{}}
onChange={handleSelectionChanged}
options={options ?? []}
getOptionLabel={x => (!x ? '' : x?.name ?? '')}
renderInput={params => <TextField {...params} label="" />}
/>
If you are using objects, you can use the following code to clear the field.
Ensure you add isOptionEqualToValue:
<Autocomplete
style={{ width: 250 }}
multiple
id="checkboxes-tags-demo"
options={list}
isOptionEqualToValue={(option, newValue) => {
return option.id === newValue.id;
}}
value={selected}
onChange={(e, val) => handleSelected(e, val)}
getOptionLabel={(option) => option.name}
renderInput={(params) => (
<TextField
{...params}
label="Add to Multiple"
placeholder="Favorites" />
)} />
Just set an empty array in your state through functions, and it'll be cleared.
Try this method:
use onChange method and pass third parameter reason and compare to clear text if reason is clear then executed this function.
<Autocomplete
onChange={(event, newValue, reason) => {
if (reason === 'clear') {
console.log("Put your clear logic here: this condition executed when clear button clicked")
setValue({ title: '', year: '' }) //for reset the value
return
}
}}
/>
One easy way to do this is to pass these props to autocomplete like this:
onChange={handleSkillChange}
inputValue=''
clearOnBlur={true}
onChange is an event handler, which stores the value in the state.
inputValue='' helps to ensure that the text field inside autocomplete will always be empty
clearOnBlur={true} helps to clear the value of the autocomplete component when it loses focus.

Implementing react hooks useState in material-ui Tab container not working

I am trying to implement react-hooks useState in material-ui/tabs component. I am able to do it with function handleChange, but I am trying to learn to implement hooks. useState is working for input field but not for material-ui tabs onChange function. Following is my code:
const [value, setValue] = useState(0)
<Tabs
value={value}
onChange={(event) => setValue(event.target.value)}
variant="scrollable"
scrollButtons="on"
indicatorColor="primary"
textColor="primary"
>
<Tab label="All" />
{subjects.map((subject) => (
<Tab label={subject.subjectName} />
))}
</Tabs>
I tried console log with useEffect, and it returns undefined onChange
The main issue I see is that
onChange={(event) => setValue(event.target.value)}
should instead be:
onChange={(event, newValue) => setValue(newValue)}
The event in this case is just a click event and the target will be the particular DOM element clicked on which could be any one of several elements (e.g. span, button) that make up the Tab. Unlike with input elements, none of the DOM elements in the Tab have a value property, so Material-UI passes the value as a separate argument to the onChange function.
Here is the relevant code from the Tab component:
handleChange = event => {
const { onChange, value, onClick } = this.props;
if (onChange) {
onChange(event, value);
}
if (onClick) {
onClick(event);
}
};
You'll find the onChange signature documented in the documentation for the Tabs props: https://material-ui.com/api/tabs/#props
onChange func Callback fired when the value changes. Signature: function(event: object, value: number) => void
Here's a working example based on your code:

Resources