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)
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.
⛔️ 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.
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.
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.
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.
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.
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.
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.
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
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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With