Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to maintain state between mount/unmount? #4345

Closed
ccorcos opened this issue Jul 11, 2015 · 15 comments
Closed

How to maintain state between mount/unmount? #4345

ccorcos opened this issue Jul 11, 2015 · 15 comments

Comments

@ccorcos
Copy link

ccorcos commented Jul 11, 2015

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?

@zpao
Copy link
Member

zpao commented Jul 11, 2015

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.

@zpao zpao closed this as completed Jul 11, 2015
@ccorcos
Copy link
Author

ccorcos commented Jul 11, 2015

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...

@ccorcos
Copy link
Author

ccorcos commented Jul 11, 2015

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...

@ccorcos
Copy link
Author

ccorcos commented Jul 11, 2015

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

@jimfb
Copy link
Contributor

jimfb commented Jul 11, 2015

@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 #3653 (comment)

@ccorcos
Copy link
Author

ccorcos commented Jul 12, 2015

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.

@jimfb
Copy link
Contributor

jimfb commented Jul 12, 2015

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.

@dcousens
Copy link
Contributor

@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.

@ccorcos
Copy link
Author

ccorcos commented Jul 12, 2015

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...

@jimfb
Copy link
Contributor

jimfb commented Jul 12, 2015

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

@amannn
Copy link
Contributor

amannn commented Nov 19, 2015

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

@ccorcos
Copy link
Author

ccorcos commented Nov 19, 2015

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.

@beterfysiek
Copy link

beterfysiek commented Aug 19, 2020

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

@infostreams
Copy link

I've made a small library to deal with this, it allows you to write code like

    componentDidMount() {
        // Restore the component state as it was stored for key '3' in section 'page'
        //
        // You can choose 'section' and 'key' freely, of course.
        this.setState(this.props.componentstate.get('page', 3))
    }

    componentDidUnmount() {
         // store this state's component in section 'page' with key '3'
        this.props.componentstate.set('page', 3, this.state)
    }

which would be useful for a number of use cases, for example multi-page forms. Hope it is of use to someone.

@itayperry
Copy link

itayperry commented Aug 5, 2022

Excuse me for barging in :)
Regarding React 18 👾 does anyone know how preserve a component's state while moving it around, meaning without it being unmounted :) I would like to make components "jump" between groups/trees without recreating them.
Is there maybe a hook that can help? I found a package that does that: https://github.com/paol-imi/react-reparenting and an old quote by Abramov saying it's not planned to happen..
image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants