2

I'm making a sliding tree navigation component in React. When going forward the new list slides in from the right, the old one slides out to the left. I do it with <TransitionGroup> and <CSSTransition>. I am removing the old list from the array and adding the new one to it, every item is properly keyed. It works. However when going back it doesn't.

I have a demo here: https://codesandbox.io/s/1wq71p5ly3

When navigating back, in <App> component state I am flipping a boolean value and supply it to <TreeContainer> component so inside <CSSTransition> a different CSS class could be applied (basically reversing the direction of the translate).

When removing and adding from an array (and then setting state) for some reason the <TreeContainer> is called 3 times (I guess one for removal, one for addition and one for... I don't know). This is still fine when navigating forward, but when I go backward as you can see in the demo, I logged the boolean value inside <TreeContainer> after explicitly setting its value inside the parent with setState and this is what I got:

false
true
false

It is still called three times, but once the boolean value is true, even though I set it explicitly to false.

I have no clue why does it work in one direction and why doesn't in the other. It must be some minor thing I am overlooking, but I can't see it. I would really appreciate your help.

1 Answer 1

1

<TreeContainer> is called 3 times because: it's called for each new item and for each item that will be removed.

When you go Back new state is never passed to <TreeContainer>, but CSS Transition excutes it anyway with old forward prop.

To change it you need to have two step state transition. I recommend using setState callback like this:

  navForward(branch: INavTreeElement) {

    var fullTreeUI = this.state.treeUI.slice();
    var filteredDataTree = findById(this.props.NavDataTree, branch.id);
    var nextTreeUI: any = {
      branch: filteredDataTree.children.map((i: any) => { return { name: i.name, id: i.id }; }),
      id: Math.random().toString(36).substring(2)
      + (new Date()).getTime().toString(36)
    };

    treeStack.push(this.state.treeUI.slice()[0]);
    fullTreeUI.push(nextTreeUI);
    fullTreeUI.shift();

    this.setState({
      forward: true,
    }, () => {
      this.setState({
        treeUI: fullTreeUI,
        depth: (this.state.depth + 1)
      });
    });
  }

  navBackward() {

    var fullTreeUI: any = this.state.treeUI.slice();
    fullTreeUI.unshift(treeStack.pop());
    fullTreeUI.pop();

    this.setState({
      forward: false,
    }, () => {
      this.setState({
        treeUI: fullTreeUI,
        depth: (this.state.depth - 1)
      });
    });
  }

This fixes your issue. Cheers

Sign up to request clarification or add additional context in comments.

5 Comments

Thanks, it works flawlessly. What do you mean by When you go Back new state is never passed to <TreeContainer> ? When I click the back button I set the state of <App> and <TreeContainer> receives it as a prop (the forward boolean). Or this callback thing is due to some asynchronicity nature of setState?
The problem is that when you change at the same time state properties forward and treeUI - the old value of treeUI (the one that's going to disappear) never gets the new forward property. In your render method you have: {this.state.treeUI} when you change at the same time {this.state.treeUI}and {this.state.forward} the component that will be unmount will never know that direction is going to change.
See this codesandbox.io/s/k10wqy4y9o I've added additional console.log in TreeContainer. Maybe it will show you what's the problem. EDIT: And yes, because setState is asynchronious it goes like that: set new state of forward => tell currently mounted TreeContainer that direction has changed (new value for forward property is passed`) => set new state of treeUI to display new navigation item.
Thanks, I think I get it. But now the <App> component renders twice (for each setState). Do you know how to avoid this? Or it's not that big of a deal?
Actually it is required for this solution to work. First render change TreeContainer direction (so it will be animated properly), second render change navigation items, but do not worry it's not a big deal. React has it's virtual DOM and knows what need update.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.