Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React rebuild tree with HOC components (causing input field focus loss)

Tags:

reactjs

I'm using Higher Order Components to decorate my components.

const HOC = (WrappedComponent) => (props) => {
    return (
        <span>
            <p>HOC Comp</p>
            <WrappedComponent {...props}/>
        </span>
    )
}

I do like this pattern explained here: React Higher Order Components in depth

However I have a problem because the HOC causing React to recreate my component tree instead of updating the tree. This is nicely explained here React Reconciliation. HOC returns an anonymous function whereby React doesn't know it is actually rendering the same component. This is bad for performance and makes my input field lose focus.

How could I use HOC components without React recreating my tree on every render()?

Example code:

class Input extends React.Component {
    componentWillMount() {
        console.log('input component will mount');
    }

    componentWillUnmount() {
        console.log('input component will unmount');
    }

    render() {
        return (
            <span>
                <input value={this.props.value} onChange={this.props.onChange}/>
            </span>
        );
    }
}

const HOC = (WrappedComponent) => {
    const Help = (props) => {
        return (
            <span>
                <WrappedComponent {...props}/>
                <p>{props.help}</p>
            </span>
        )
    };

    return Help;
}

class MyComponent extends React.Component {
    constructor (props) {
        super(props);
        this.state = {value : 'start value'}
    }

    onChange(event) {
        this.setState({value : event.target.value});
    }

    render() {
        const Element = HOC(Input);
        return (
            <span>
                <Element
                    value={this.state.value}
                    onChange={this.onChange.bind(this)}
                />
            </span>
        )
    }
}

ReactDOM.render(
    <MyComponent />,
    document.getElementById('container')
);

See Fiddle example (see in your browser's console to see the mount and unmount logs from the input component every time you change the input and lose your focus)

like image 620
Stefan van de Vooren Avatar asked Nov 14 '16 14:11

Stefan van de Vooren


People also ask

Why does the input field lose focus after typing a character React?

it is because you are rendering the form in a function inside render(). Every time your state/prop change, the function returns a new form. it caused you to lose focus.

Do components re-render when props change?

⛔️ Re-renders reason: props changes (the big myth)In order for props to change, they need to be updated by the parent component. This means the parent would have to re-render, which will trigger re-render of the child component regardless of its props. Only when memoization techniques are used ( React.

What is shouldComponentUpdate in React?

shouldComponentUpdate(nextProps, nextState) Use shouldComponentUpdate() to let React know if a component's output is not affected by the current change in state or props. The default behavior is to re-render on every state change, and in the vast majority of cases you should rely on the default behavior.

How do you refresh a component mount?

In other words, component does not reload so componentDidMount() won't be called again. Better to use state management like Redux or Mobx. Let them handle the changes instead of reading from asyncStorage. If a Mobx Store gets updated it'll automatically refresh your values wherever you are accessing it.

How to avoid losing focus in react programming?

If the higher list component which contains all the recipient inputs used recipients.length as part of its key, then adding or removing recipients would cause losing focus as well. Becuase react would re-render the entire list when the key changed. Try to avoid such kind of mistakes. You don’t need to overcomplicate your keys.

What does it mean when the input component loses focus?

Since input component loses focus, it means it is rerendered. It happens only when one of input component has changed type or upper tree is changed. In this case react does complete rerender. So I guess one of the parent components [ReduxTable, ReduxTableHeader] is defined on every render. like you do for TBody.

Why do I lose focus when I add or remove recipients?

Also, remember that such problem can be caused by invalid keys higher in the hierarchy. If the higher list component which contains all the recipient inputs used recipients.length as part of its key, then adding or removing recipients would cause losing focus as well.

What are some interesting problems you've had with react?

I recently stumbled upon an interesting problem. React.js was loosing focus on an input while the user was typing. Here is a video of that problem. If playback doesn't begin shortly, try restarting your device.


2 Answers

You don't have to create Element in the render function. Instead you can create it in the constructor:

class MyComponent extends React.Component {
    constructor (props) {
        super(props);
        this.state = {value : 'start value'};
        this.element = HOC(Input);
    }
...

And use it in your render function like this:

        <span>
            <this.element
                value={this.state.value}
                onChange={this.onChange.bind(this)}
            />
        </span>

If needed you can update this.element in componentWillReceiveProps() or componentWillUpdate().

UPDATE: fiddle

like image 118
Simon Groenewolt Avatar answered Oct 26 '22 01:10

Simon Groenewolt


This is the way how I extended a functional component with a controlled input element via children and it does not lose focus.

/* import React, { Fragment, useState } from 'react' */
const { Fragment, useState } = React

/* HOC */
const withInput = Base => (props) => {
  const [inputValue, setValue] = useState()
  return <Base children={
    <Fragment>
      {props.children}<br />
      <input
        value={inputValue}
        onChange={({ target: { value }}) => setValue(value)}
        placeholder="input from hoc" /><br />
       <span>inputValue: {inputValue}</span>
    </Fragment>
  } />
}

/* Component */
const MyComponent = ({children}) => (
  <span>
    <span>own elements</span><br />
    {children}
  </span>
)

const MyComponentWithInput = withInput(MyComponent)

ReactDOM.render(<MyComponentWithInput>passed children</MyComponentWithInput>, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
like image 37
gazdagergo Avatar answered Oct 26 '22 00:10

gazdagergo