React: How to maintain state between mount/unmount?

Created on 11 Jul 2015  路  13Comments  路  Source: facebook/react

I'm not really getting the answer I'm looking for on StackOverflow so I thought I'd try here.

Is it possible to instantiate a component, then mount, unmount, and remount it allowing the component to maintain its internal state/context?

Most helpful comment

The motivation here is to be able to push and pop components on a UINavigationController-like component. So it would be weird if some kind of drawer was open, or some forms partially filled out, and when you pop back to the form, then everything is reset. Similarly with a UITabBarController-like component.

Hiding the components rather than unmounting is a viable solution. But this won't play nicely with existing code that relies on the mounting/unmounting lifecycle...

All 13 comments

No. The state is part of the instance. If state should be maintained then it probably doesn't belong in the component itself and should live in some data store, or the parent (and passed in as props).

You could do something a little bit clever and have a hidden prop instead of unmounting. Then just render null when hidden or something. But that still involves moving _some_ state back up to the parent.

The motivation here is to be able to push and pop components on a UINavigationController-like component. So it would be weird if some kind of drawer was open, or some forms partially filled out, and when you pop back to the form, then everything is reset. Similarly with a UITabBarController-like component.

Hiding the components rather than unmounting is a viable solution. But this won't play nicely with existing code that relies on the mounting/unmounting lifecycle...

Now that I think about it, you make a good suggestion. I think I just need a mixin like this.

HiddenMixin = {
  propTypes: {
    hidden: React.PropTypes.bool.isRequired
  },
  componentWillMount: function() {
    if (this.props.hidden) {
      this.componentWillAppear && this.componentWillAppear()
    }
  },
  componentWillReceiveProps: function(nextProps) {
    if (this.props.hidden != nextProps.hidden) {
      if (nextProps.hidden) {
        this.componentWillAppear && this.componentWillAppear()
      } else {
        this.componentWillDisappear && this.componentWillDisappear()
      }
    }
  }
}

Then, in the render function, just check to see if it should be hidden. But this way, the component is still instantiated even if it doesnt render anything...

But then I can't use CSSTransitionGroup to do push/pop animation... :/

@ccorcos If you render null as hidden, you will loose the child component state. If you need full control of internal state, you can always pull it out as per https://github.com/facebook/react/issues/3653#issuecomment-92526513

Hmm. Its not that I want access to the internal state from elsewhere, I just want to be able to re-mount a component with the same state as before. I'm trying to make something similar to NavigatorIOS from React Native. Here's what I have so far.

NavVC = React.createClass({
  displayName: 'NavVC',
  propTypes: {
    initialRoute: React.PropTypes.object.isRequired,
    renderScene: React.PropTypes.func.isRequired
  },
  getInitialState: function() {
    return {
      stack: [this.renderScene(this.props.initialRoute)]
    }
  },
  renderScene: function(route) {
    this.props.renderScene(this, route)
  },
  push: function(route) {
    component = this.renderScene(route)
    stack = React.addons.update(this.state.stack, {$push:component})
    this.setState({stack: stack})
  },
  pop: function() {
    last = this.state.stack.length - 1
    stack = React.addons.update(this.state.stack, {$splice:[[last, 1]]})
    this.setState({stack: stack})
  },
  render: function() {
    last = this.state.stack.length - 1
    return this.state.stack[last]
  }
})

We can wrap what is rendered in a CSSTransitionGroup to animate it as well. But the problem is, the state of the view is lost when you push a new view and the pop it...

Apparently you can't keep an instantiated component and remount it though so I'm working on some other ideas.

If you can get/set the 'internal' state, you can save it in your component/application, and use it to restore the child component tree whenever you want. It's the fully general/flexible solution, but requires a little extra work.

@jimfb on a similar note to the above, is there any reason why componentWillEnter and componentWillLeave aren't called by CSSTransitionGroup for the children components?
I understand its a behaviour for people implementing from TransitionGroup, but I could forsee it being a very useful addition for CSSTransitionGroup too.

Yeah, so I came up with way of saving and restoring the state.

http://jsfiddle.net/ccorcos/t86axd6L/

The nice thing is that it can be instantiated as a prop, the pushed to the navVC. Then we don't have to worry about it ever again...

@dcousens, I don't know, that's probably a question for @zpao or @spicyj

I was also experimenting with a solution. My results are outlined in this Stack Overflow post.

So I think there are two solution with different perspectives -- I still havent come to a conclusion on which i like more.

1) Use cursors. @amannn, you solution is similar to this. This is how clojurescript does it. Its interesting and powerful and has good encapsulation.
2) Use the Elm Architecture which involves a little extra work but has amazing powers of abstraction. Redux tries to align itself with this pattern.

If anyone is looking for a solution. Just use a variable outside the function like:

let rememberMe = null

const myFunctionalComponent = (props) => {
const [ state, setState ] = useState(rememberMe)

const setMyState = (value) => {
setState(1)
rememberMe = 1
}
}

Its an old topic but maybe usefull to anyone

Was this page helpful?
0 / 5 - 0 ratings