SOLUTION #1 (with key and remount):
You probably need to make your current component remount on each outer props update by providing it with a key, based on your incoming prop: currentLevel
. It would looks like:
class Wrapper ... {
...
render() {
const { currentLevel } = this.props;
return (
<NameEditor key={currentLevel} {...currentLevel} />
)
}
}
export default Wrapper
...and make some extra changes on your component to block derived props replacing by telling it - is it a first time render or not (because we plan to control its state
from inside only and from outer only by remount, when it really so):
import PropTypes from 'prop-types'
import React from 'react'
export class NameEditor extends React.Component {
static propTypes = {
currentLevel: PropTypes.number
}
static defaultProps = {
currentLevel: 0
}
constructor(props) {
super(props)
this.state = {
currentLevel: 0,
isFirstRender: false
}
}
static getDerivedStateFromProps(nextProps, prevProps) {
if (!prevProsp.isFirstRender) {
return {
currentLevel: nextProps.currentLevel,
isFirstRender: true
};
}
return null;
}
_handleInputChange = e => {
this.setState({
currentLevel: e.target.value
})
}
render() {
const { currentLevel } = this.state
return (
<input
placeholder={0}
value={currentLevel}
onChange={this._handleInputChange}
/>
)
}
}
export default NameEditor
So, by that scenario you'll achieve chance to manipulate your component state by manually inputed value from form.
SOLUTION #2 (without remount by flag):
Try to set some flag to separate outer (getDerived...) and inner (Controlled Comp...) state updates on each rerender. For example by updateType
:
import PropTypes from 'prop-types'
import React from 'react'
export class NameEditor extends React.Component {
static propTypes = {
currentLevel: PropTypes.number
}
static defaultProps = {
currentLevel: 0
}
constructor(props) {
super(props)
this.state = {
currentLevel: 0,
updateType: 'props' // by default we expecting update by incoming props
}
}
static getDerivedStateFromProps(nextProps, prevProps) {
if (!prevState.updateType || prevState.updateType === 'props') {
return {
updateType: 'props',
currentLevel: nextProps.currentLevel,
exp: nextProps.exp
}
}
if (prevState.updateType === 'state') {
return {
updateType: '' // reset flag to allow update from incoming props
}
}
return null
}
_handleInputChange = e => {
this.setState({
currentLevel: e.target.value
})
}
render() {
const { currentLevel } = this.state
return (
<input
placeholder={0}
value={currentLevel}
onChange={this._handleInputChange}
/>
)
}
}
export default NameEditor
P.S.
It's probably an anti-pattern (hope Dan will never see this), but I can't find a better solution in my head now.
SOLUTIONS #3:
See Sultan H. post under this one, about controlled logic with explicit callback from wrapper component.