I'm building a an app in React/Redux and I have some input fields that lose focus after each key stroke. My code looks like this:
<General fields={props.general}
onChange={value => props.updateValue('general', value)} />
<FormWrapper>
<Network fields={props.network}
onChange={value => props.updateValue('network', value} />
</NetworkTableForm>
</FormWrapper>
General and Network just contain different sections of the form and FormWrapper can wrap a subsection of the form and provide some interesting visualisations. The problem is that all of the fields in General work fine but the fields in Network lose focus after each keystroke. I've already tried appending keys to a bunch of different elements such as General Network, FormWrapper and my components wrapping my input fields and the reason that I'm not using redux-form is because it doesn't work well with the UI framework that I'm using(Grommet). Any idea what could be causing this?
Edit: Adding more code as per request. Here's the full component:
const InfoForm = (props) => {
let FormWrapper = FormWrapperBuilder({
"configLists": props.configLists,
"showForm": props.showForm,
"loadConfiguration": props.loadConfiguration,
"updateIndex": props.updateIndex,
"metadata": props.metadata})
return (
<CustomForm onSubmit={() => console.log('test')}
loaded={props.loaded} heading={'System Details'}
header_data={props.header_data}
page_name={props.general.name} >
<General fields={props.general}
onChange={(field, value) => props.updateValue('general', field, value)} />
<FormWrapper labels={["Interface"]} fields={["interface"]} component={'network'}>
<Network fields={props.network}
onChange={(field, value) => props.updateValue('network', field, value)} />
</FormWrapper>
<User fields={props.user}
onChange={(field, value) => props.updateValue('user', field, value)} />
</CustomForm>
)
}
export default InfoForm
Here's an example of a form section component:
const Network = ({fields, onChange}) => (
<CustomFormComponent title='Network'>
<CustomTextInput value={fields.interface} label='Interface'
onChange={value => onChange('interface', value)} />
<CustomIPInput value={fields.subnet} label='Subnet'
onChange={value => onChange('subnet', value)} />
<CustomIPInput value={fields.network} label='Network'
onChange={value => onChange('network', value)} />
<CustomIPInput value={fields.gateway} label='Gateway'
onChange={value => onChange('gateway', value)} />
<CustomIPInput value={fields.nat_ip} label='NAT'
onChange={value => onChange('nat_ip', value)} />
</CustomFormComponent>
)
export default Network
Here's an example of one of the custom inputs:
const CustomTextInput = ({label, value, onChange}) => (
<FormField label={<Label size='small'>{label}</Label>}>
<Box pad={{'horizontal': 'medium'}}>
<TextInput placeholder='Please Enter' value={value}
onDOMChange={event => onChange(event.target.value)}
/>
</Box>
</FormField>
)
export default CustomTextInput
And here's the FormWrapperBuilder function:
const FormWrapperBuilder = (props) =>
({component, labels, fields, children=undefined}) =>
<VisualForm
configLists={props.configLists[component]}
showForm={visible => props.showForm(component, visible)}
loadConfiguration={index => props.loadConfiguration(component, index)}
updateIndex={index => props.updateIndex(component, index)}
metadata={props.metadata[component]}
labels={labels} fields={fields} >
{children}
</VisualForm>
export default FormWrapperBuilder
All of these are kept in separate files which may affect the rerendering of the page and it's the functions that are wrapped in the FormWrapper that lose focues after a state change. Also I've tried adding keys to each of these components without any luck. Also here's the functions in my container component:
const mapDispatchToProps = (dispatch) => {
return {
'updateValue': (component, field, value) => {
dispatch(updateValue(component, field, value))},
'showForm': (component, visible) => {
dispatch(showForm(component, visible))
},
'loadConfiguration': (component, index) => {
dispatch(loadConfiguration(component, index))
},
'pushConfiguration': (component) => {
dispatch(pushConfiguration(component))
},
'updateIndex': (component, index) => {
dispatch(updateIndex(component, index))
}
}
}
Edit 2: Modified my custom text input as such as per Hadas' answer. Also slathered it with keys and refs for good measure
export default class CustomTextInput extends React.Component {
constructor(props) {
super(props)
this.state = {value: ""}
}
render() {
return (
<FormField key={this.props.label} ref={this.props.label} label={<Label size='small'>{this.props.label}</Label>}>
<Box key={this.props.label} ref={this.props.label} pad={{'horizontal': 'medium'}}>
<TextInput placeholder='Please Enter' value={this.state.value} key={this.props.label} ref={this.props.label}
onDOMChange={event => { this.props.onChange(event.target.value); this.state.value = event.target.value }} />
</Box>
</FormField>
)
}
}
React is re-rendering the DOM on each key stroke. Try setting value to a variable attached to the component's state like this:
<TextInput
style={{height: 40, borderColor: 'gray', borderWidth: 1}}
onChangeText={(text) => this.setState({text})}
value={this.state.text}></TextInput
Related
I have a mui autocomplete component. I am trying to reuse this autocomplete component with a form where formik validation added.
My autocomplete component is,
const CustomAutoCompleteField = props => {
const {rerenderAutocomplete, data, refetchCategoryData, autoCompleteFieldsData, inputLabel, autoCompleteFieldsInputOnChange , onTouch, onErrors,fieldProps, onBlur} = props
const [textFieldData, setTextFieldData] = useState(null)
const onChangeHandler = (event, value) =>{
}
return (
<>
<Autocomplete
key={rerenderAutocomplete}
// value={onEdit && data}
isOptionEqualToValue={(option, value) => option.name === value.name}
onBlur={onBlur}
onChange={onChangeHandler}
fullWidth
id="tags-outlined"
options={autoCompleteFieldsData ? autoCompleteFieldsData : top100Films }
getOptionLabel={(option) => option.name}
filterSelectedOptions
renderInput={(params) => (<TextField
required
{...params}
label={inputLabel}
onChange={textFieldInputOnChange}
error={Boolean(onTouch && onErrors)}
helperText={onTouch && onErrors}
{...fieldProps}
/>)}
/>
</>
);
};
Here I am passing formik attributes in side props which are, onTouch, onErrors,fieldProps, onBlur.
In My Parent component, i am using this autocomplete field by giving props, which are,
<CustomAutoCompleteField inputLabel='Select Category'
onBlur={addNewServiceFormik.handleBlur}
onTouch={addNewServiceFormik.touched.selectedCategoryName}
onErrors={addNewServiceFormik.errors.selectedCategoryName}
fieldProps={addNewServiceFormik.getFieldProps('selectedCategoryName')}
/>
I don,t know why, when i click submit on my form, this autocomplete doesn't show any helper text as per formik validation.
I simply created a component for this
In the form your must pass FormikProvider for use with FormikContext
import { TextField } from '#mui/material';
import Autocomplete from '#mui/material/Autocomplete';
import { useFormikContext } from 'formik';
import React from 'react';
type OptionsValues = {
title: string,
value: string
}
type Props = {
id: string,
name: string,
label: string,
options: OptionsValues[]
}
function MuiltSelect(props: Props) {
const { options, name, label, id } = props
const formik = useFormikContext();
return (
<Autocomplete
{...props}
multiple
options={options}
getOptionLabel={(option: any) => option.title}
onChange={(_, value) => formik.setFieldValue(name, value)}
filterSelectedOptions
isOptionEqualToValue={(item: any, current: any) => item.value === current.value}
renderInput={(params) => (
<TextField
{...params}
id={id}
name={name}
label={label}
variant={"outlined"}
onChange={formik.handleChange}
error={formik.touched[name] && Boolean(formik.errors[name])}
helperText={formik.errors[name]}
value={formik.values[name]}
fullWidth
/>
)
}
/>
)
}
export default MuiltSelect
check gist
https://gist.github.com/Wellers0n/d5dffb1263ae0fed5046e45c47a7c4a7
I am making a custom input component
const CustomInput = (props) => {
console.log(props);
return (
<TextInput
{...props}
ref={props.ref}
placeholder={props.placeholder}
style={{ ...styles.text, ...props.style }}
/>
);
};
in the file I want to use it I have
const ForgottenPasswordScreen = (props) => {
...
const inputRef = React.createRef();
useEffect(() => {
inputRef.current.focus();
}, []);
...
<CustomInput
placeholder={"E-mail..."}
value={email.value}
ref={inputRef}
onChangeText={(text) => setEmail({ value: text, error: "" })}
/>
...
If I am using normal TextInput there is no problem, but when I try to use my CustomInput,
i get the error
TypeError: null is not an object (evaluating 'inputRef.current.focus')
I don't get why ref={props.ref} is not doing the job. I thought that ref will be passed to my component too. How to pass ref properly ?
The ref is not inside of props. When using ref as a prop, the FunctionComponents should be created with forwardRef() which takes a function that has two arguments, props and ref.
Here's the example from the documentation https://reactjs.org/docs/forwarding-refs.html
const FancyButton = React.forwardRef((props, ref) => (
<button ref={ref} className="FancyButton">
{props.children}
</button>
));
// You can now get a ref directly to the DOM button:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;
So with this we can determine if we want it to select the input or not
the reason is that the ref cannot be passed down as it is a ref to that component unless you use React.forwardRef but this is a way without forwardRef
import { useEffect, useRef } from "react";
import "./styles.css";
const InsantSelectInput = (props) => {
const inputRef = useRef(null)
useEffect(() => {
if(inputRef && inputRef.current)
inputRef.current.focus()
}, [inputRef])
return <input {...props} ref={inputRef} placeholder={props.placeholder} />;
}
const CustomInput = (props) => {
return <>
{props.isSelectedInput && <InsantSelectInput {...props} />}
{!props.isSelectedInput && <input {...props} placeholder={props.placeholder} />}
</>
};
export default function App() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<CustomInput
placeholder={"E-mail..."}
value={""}
isSelectedInput
onChangeText={(text) => console.log({ value: text, error: "" })}
/>
</div>
);
}
OR with forwardRef
const CustomInput = React.forwardRef((props, ref) => {
return <>
<TextInput
{...props}
ref={ref}
placeholder={props.placeholder}
style={{ ...styles.text, ...props.style }}
/>
});
const ForgottenPasswordScreen = (props) => {
...
const inputRef = React.createRef();
useEffect(() => {
inputRef.current.focus();
}, []);
...
<CustomInput
placeholder={"E-mail..."}
value={email.value}
ref={inputRef}
onChangeText={(text) => setEmail({ value: text, error: "" })}
/>
...
I am facing a problem with my formik fields and need to use FastFields in some situations and Fields in others. Therefor my FormikControl, a class for building formik inputs needs to switch between and as wrappers. I am not sure how to do this.
Here is part of the class:
const FormikControl = (props) => {
const {
name,
type,
label,
disabled,
fullWidth,
isDynamic, // Field (true) or FastField
} = props;
const classes = useStyles();
const inputProps = {};
switch (type) {
case 'text':
return (
<>
<ComplexField name={name} isDynamic={isDynamic} {...props}> // meant to be the wrapper that switches
{({ field }) => {
return (
<TextField
{...field}
variant="standard"
label={label}
fullWidth={fullWidth}
disabled={disabled}
InputLabelProps={{
shrink: true,
}}
InputProps={inputProps}
value={field.value || ''}
/>
);
}}
</ComplexField>
<ErrorMessage error name={name} component={FormHelperText} />
</>
);
ComplexField is meant to be the flexable wrapper and needs be able to switch between Field and FastField based on "isDynamic".
My best try was this:
const ComplexField = ({ name, children, isDynamic, ...props }) => {
return isDynamic ? (
<Field name={name} props={props}>{({ field }) => children({ ...props, field })}</Field>
) : (
<FastField name={name} props={props}>{({ field }) => children({ ...props, field })}</FastField>
);
};
Didn't work :(
<ComplexField name={name}>
You're not passing there the prop "isDynamic"
Is it intentional? Because like that it's obvious that doesnt work
<ComplexField name={name} isDynamic={isDynamic}>
If you want to use a better pattern, read that Conditionally render react components
I'm using MUI library to create my React Js app.
Here I'm using the controlled Text Field component to create a simple search UI.
But there is something strange. The Text Field component loses focus after its value is changed.
This is my first time facing this problem. I never faced this problem before.
How this could happen? And what is the solution.
Here is the code and the playground: https://codesandbox.io/s/mui-autocomplete-lost-focus-oenoo?
Note: if I remove the breakpoint type from the code, the Text Field component still loses focus after its value is changed.
It's because you're defining a component inside another component, so that component definition is recreated every time the component renders (and your component renders every time the user types into the input).
Two solutions:
Don't make it a separate component.
Instead of:
const MyComponent = () => {
const MyInput = () => <div><input /></div>; // you get the idea
return (
<div>
<MyInput />
</div>
);
};
Do:
const MyComponent = () => {
return (
<div>
<div><input /></div> {/* you get the idea */}
</div>
);
};
Define the component outside its parent component:
const MyInput = ({value, onChange}) => (
<div>
<input value={value} onChange={onChange} />
</div>
);
const MyComponent = () => {
const [value, setValue] = useState('');
return (
<div>
<MyInput
value={value}
onChange={event => setValue(event.target.value)}
/>
</div>
);
};
For MUI V5
Moved your custom-styled code outside the component
For example:
import React from 'react';
import { useTheme, TextField, styled } from '#mui/material';
import { SearchOutlined } from '#mui/icons-material';
interface SearchInputProps { placeholder?: string; onChange: (event: React.ChangeEvent<HTMLInputElement>) => void; value: string; dataTest?: string; }
const StyledSearchInput = styled(TextField)(({ theme }: any) => { return {
'& .MuiOutlinedInput-root': {
borderRadius: '0.625rem',
fontSize: '1rem',
'& fieldset': {
borderColor: `${theme.palette.text.secondary}`
},
'&.Mui-focused fieldset': {
borderColor: `${theme.palette.primary}`
}
} }; });
const SearchInput: React.FC<SearchInputProps> = ({ placeholder = 'Search...', onChange, value, dataTest, ...props }) => { const theme = useTheme();
return (
<StyledSearchInput
{...props}
onChange={onChange}
placeholder={placeholder}
variant="outlined"
value={value}
inputProps={{ 'data-testid': dataTest ? dataTest : 'search-input' }}
InputProps={{
startAdornment: (
<SearchOutlined
sx={{ color: theme.palette.text.secondary, height: '1.5rem', width: '1.5rem' }}
/>
)
}}
/> ); };
export default SearchInput;
Be careful about your components' keys, if you set dynamic value as a key prop, it will also cause focus lost. Here is an example
{people.map(person => (
<div key={person.name}>
<Input type="text" value={person.name} onChange={// function which manipulates person name} />
</div>
))}
What I am trying to achieve is focusing on TextInput present in child component when clicked on an icon present in parent file. I have tried using refs and forward refs but that means i have to wrap the component in forwardref which i am trying to avoid.
const InputText = React.forwardRef((props, ref) => (
<input ref={ref} {...props} />
));
My child file looks like this:
export const TextField = ({
label,
placeholder = "",
id = "",
isSecure = false,
disabled = false,
handleBlur,
handleChange,
value,
...props
}: IProps): React.ReactElement => {
const { labelStyle, fieldStyle, status=false, search=false,inputStyle, errorStyle } =
props;
//some statements
<View style={styles.elementsContainer}>
<TextInput //I want to focus on this
.......
.
.
onChangeText={handleChange(props.name)}
onBlur={handleBlur(props.name)}
/>
</View>
);
};
export default TextField;
Below is my parent file where I have an icon and on click of it I trying this textfield to be focused.
return (
<Formik
initialValues={initialValues}
onSubmit={(values, { setSubmitting }) => {
setSubmitting(false);
}}
>
{(formikProps: FormikProps<{ searchText: string }>) => (
<View style={status?styles.highlight:[styles.container,textFieldDimensions]}>
<TouchableOpacity onPress={addressFocus}> //This is the icon on click of it i am trying textfield child component to be focused.
<Icon testID="home" name={IconNames.home} />
</TouchableOpacity>
<View style={styles.textfieldContainer}>
<TextField //CHild component
handleChange={formikProps.handleChange}
status={status}
handleBlur={() => searchFunction(formikProps.values.searchText)}
value={formikProps.values.searchText}
name="searchText"
placeholder={placeholder}
search={search}
label="Search :"
id="searchText"
fieldStyle={[styles.field,fieldStyle]}
inputStyle={styles.input}
labelStyle={[styles.label, labelStyle]}
/>
</View>
</View>
)}
</Formik>
);
So after brainstorming i come to a simple approach which works for me as of now. But i am not crystal clear from ref part how its working.
so what i did is in parent file where i am passing props in child component like this:
const toggle=()=>{
setToggler(!toggler) This function changes state value which is boolean type from false to true and vice versa.
}
<TouchableOpacity onPress={toggle}> //toggle function called on onPress
<Icon testID="home" name={IconNames.home} />
</TouchableOpacity>
<TextField
toggler={toggler} //Now I am passing state toggler into child component, change of this state will re render the component
handleChange={formikProps.handleChange}
status={status}
handleBlur={() => searchFunction(formikProps.values.searchText)}
value={formikProps.values.searchText}
name="searchText"
placeholder={placeholder}
search={search}
label="Search :"
id="searchText"
fieldStyle={[styles.field,fieldStyle]}
inputStyle={styles.input}
labelStyle={[styles.label, labelStyle]}
/>
Now how i am handling refs in child component is that it focus on textinput on click of button.
All this is happening in functional component, for simplicity react rules has not been taken into account only relevant pieces of code is shown
const inputRef = useRef<TextInput>(null);
const onClickFocus = () => {
{props.toggler && inputRef.current?.focus();} //If toggler changes to true then this function will execute and perform inputRef.current?.focus();
}
onClickFocus() //Function is called here and later inputRef is utilized in textinput as ref={inputRef}
<TextInput
ref={inputRef}
placeholder={placeholder}
secureTextEntry={isSecure}
style={search? fieldStyle:[styles.inputBox, inputStyle]}
editable={!disabled}
value={value}
{...field}
onChangeText={handleChange(props.name)}
onBlur={handleBlur(props.name)}
/>