I've been digging around and I can't seem to find a proper way to focus the next TextInput in a form. I'm using React Native 0.61.5 & React 16.9.0 and have set-up the project with a Typescript template. Most of what I've found online uses earlier versions of React Native or React and I can't downgrade because it's a company project and what versions we use are up to the dev leads.
React Native's documentation https://facebook.github.io/react-native/docs/textinput#__docusaurus doesn't show a .focus() method to exist on TextInput. There's onFocus, but that occurs after the TextInput has been focused, it doesn't help us set the focus to a particular TextInput on the screen after hitting the return key.
Using refs is a sound idea:
inputRef = React.createRef<'TextInput>(); // (comma after < because it kept hiding the code)
Intellisense for hovering over inputRef:
(property) LoginForm.inputRef: React.RefObject<'TextInput> // (comma after < because it kept hiding the code)
I'm using this ref like this:
this.inputRef?.current?.focus();
but I keep getting a typescript error saying:
Property 'focus' does not exist on type 'TextInput'.
Which, given the documentation, makes sense since I couldn't find it as a property there.
Intellisense for TextInput when hovering over ref attribute:
(JSX attribute) React.ClassAttributes.ref?: string | ((instance: TextInput | null) => void) | React.RefObject | null | undefined
I want to be able to tap the return key on the android/ios virtual keyboard and have the focus shift to the next TextInput so that I can submit the form.
The object create with React.createRef() have the following shape
{ current: TextInput }
You can access the text input element by doing:
inputRef.current.focus();
<TextInput
placeholder = "FirstTextInput"
returnKeyType = { "next" }
onSubmitEditing={() => { this.secondTextInput.focus(); }}
blurOnSubmit={false}
/>
<TextInput
ref={input => { this.secondTextInput = input; }}
placeholder = "secondTextInput"
/>
Related
When I'm adding key property to the following code it breaks my CSS transition by removing class 'active' after re-rendering.
Without a key property everything works great, can somebody explain why this happens?
handleClick function is adding class 'active' on clicked element:
const handleClick = (e) => {
const {currentTarget: btn} = e;
const getAllActive = selAll('.btn-filter.active');
setActiveFilter(getAllActive);
}
Component with onClick handler and key prop:
<Splide options={{
gap: '8px',
perMove: 1,
}}>
{_.map(taxonomyServiceData, ({name, id}) => {
return (
<SplideSlide key={_.uniqueId()}>
<FilterButton onClick={(e) => {
handleClick(e);
}} categoryId={id} buttonText={name}/>
</SplideSlide>
);
})}
</Splide>
Thanks in advance!
This documentation describes exactly what the issue is: Lists and Keys
TLDR: Keys should be given to the elements inside the array to give the elements a stable identity, and the key needs to be exactly the same for the item as it was before. Using _.uniqueId() will wipe out any previous keys that existed so React will 100% rerender it.
Another major issue your code has: you should not modify classes outside React unless you have absolutely no access to the thing you're trying to modify.
This link describes how to set class names in React: FAQ: Styling
Frequently I have a record of data to display to the user, and I want to display an Edit button allowing that data to be modified:
Create Pizza:
sauce: Margherita
cheese: Gorgonzola
radius: 12
[EDIT] [ORDER]
Is there a standard pattern for this in React / Material-UI? It seems silly to implement the same view twice, once with (readonly) <div>s or whatever and once with <input>s. Is there a better way? (Perhaps there's a Material-UI component with an "editable" prop, for instance.)
Take a look at api for text field. You'll see that you have a couple of ways of doing what you are looking.
You could set disabled={true}
You also get inputProps, as an example
<TextField id="time" type="time" inputProps={{ readOnly:true }} />
All the other elements should also have one or the other or both.
I usually have a wrapper component that accepts props such as ({isReadOnly: boolean}) & within that is the "native" React MUI component & then I send back editable or a read only component back to the caller.
EDIT:
interface MyTextFieldProps extends StandardTextFieldProps {
isEditable?: boolean;
// send in any other props necessary to render your non editable
// portion
}
const MyTextField: FC<MyTextFieldProps> = (props) => {
const { isEditable, ...other } = props;
return <>{props.isEditable ? <TextField {...other}></TextField> : <span>text:{props.value}</span>}</>;
};
I have a component wrapper in antd Form.create() HOC where I want to implement validation for my react-quill editor.
<Form.Item>
{getFieldDecorator('input', {
rules: [RequiredRule],
initialValue: text,
onChange: this.onChangeText
})(
<ReactQuill
className="question-form__rich-text"
modules={quillToolbar}
/>,
)}
</Form.Item>
If I start typing inside my quill editor text field it triggers the onChangeText function which in its turn changes the local state and initiates rerender of the component
onChangeText = (_, __, ___, editor) => {
this.setState({
textVal: editor.getText(),
});
};
this.state = {
textVal: '',
};
Every time the component rerenders my text field loses focus, so I have to click inside the field to type another letter.
I noticed that it happens only if the <ReactQuill> component is wrapped by antd <Form.Item>
It also shows some weird behaviour if I put console.log inside onChangeText function and try to check what's inside the text field:
Let's say my text field is initially empty. I type letter 'a' and it calls the function 3 times. First, it shows that the text field contains letter 'a', then it calls the function again showing that the field is empty and then the 3rd time letter 'a' appears again. This behaviour persists as I keep typing.
Also, there is an warning saying addRange(): The given range isn't in document. which I have no idea what it means.
I've been struggling with that issue for a few days now, any help would be greatly appreciated. Thank you
The reason it's loses focus when keypress is probably because the quill component is recreating when rerendering. See this same question about losing focus when typing.
I can't find a sample of implementing react quill on antd using getFieldDecorator, so I made a little workaround to make it work but same output. See this working link I made.
const { getFieldDecorator, validateFields, setFieldsValue } = props.form;
const onChangeText = (text) => {
console.log("called");
text = text !== "<p><br></p>" ? text : "";
setFieldsValue({ input: text });
setTextVal(text);
};
<Form.Item>
{getFieldDecorator("input", {
rules: [{ required: true }],
})(<Input.TextArea style={{ display: "none" }} />)}
<ReactQuill
className="question-form__rich-text"
modules={quillToolbar}
defaultValue={textVal}
onChange={onChangeText}
/>
</Form.Item>
as you can see in the above code, I place the editor outside getFieldDecorator and replaced it with hidden <Input> so your set of rules will still applied. The quill component is uncontrolled component and every time it change, it will also change the value of the hidden <Input> so when you submit or get the value of the form, the value of it is the value of quill component.
Also the warning you get addRange(): the given range isn't in the document was not visible on the codesandbox. You may look into this about that.
I have a google address autcomplete and a react-bootstrap form and I can't make the types match for the ref.
class ProfileForm extends React.Component<PropsFromRedux, ProfileFormState> {
private myRef = React.createRef<FormControl<any>>();
private autocomplete?: google.maps.places.Autocomplete;
...
}
My Form control is like this:
<Form.Control ref={this.myRef} type="text" placeholder="Enter address"
defaultValue={this.props.userProfile.location!.formatted_address}
/>
Here is how I create my autocomplete:
function forceCast<T>(input: any): T {
// #ts-ignore <-- forces TS compiler to compile this as-is
return input;
}
//#ts-ignore
this.autocomplete = new google.maps.places.Autocomplete(
forceCast<HTMLInputElement>(this.myRef.current),
options);
My question is:
How do I do this without the force cast?
The google autocomplete wants an HTMLInputElement (I've tried loads of other types and stuff) but the best I can get with react-bootstrap is the FormControl (I can't figure out how to "cast" this to anything any other way than what is shown).
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}
>