John Devitt
John Devitt

Reputation: 736

React Input losing focus after keystroke

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

Upvotes: 0

Views: 3561

Answers (1)

Hadas Zeilberger
Hadas Zeilberger

Reputation: 761

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

Upvotes: 1

Related Questions