Kendall
Kendall

Reputation: 2172

Conditional rendering in React that causes remounting when toggled

I have been struggling with React recently, trying to understand how to properly control conditionally rendered elements (such as dialogs) that can be displayed by several different things. For example, a list of items may each have their own modal content but still share the same modal. I simply hide the modal (display: none) when not needed and change the display when required.

The problem comes when I need to reinitialize the modal each time it is shown. Say, for example, the modal contains a series of tabs with the current tab index stored in the modal state. Upon closing and reopening the modal I expect that the current tab index would reset to zero, but this is not the case; rather it is whatever it was left at before hiding the modal.

The rather obvious reason for this is that the modal is only being mounted once, when it is initially rendered. Subsequent render updates do not rerun the mounting lifecycle methods. Obviously, the solution must involve remounting the component whenever desired (typically when shown again).

I've come up with a possible example – the Dialog should only unmount and remount when isDialogShown changes. I'm fairly certain React is capable of noticing this and properly running the lifecycle methods upon a change in the isDialogShown flag.

...
const ParentElement = ({ isElementShown }) => {
  return (
    {!Boolean(isElementShown) 
      ? null
      : <ConditionalElement prop={stuff} />
    }
  );
}
...

CodePen Example - Conditional Rendering

However, I'm not sure if this is the best way to do it. I wish there was a way to pass a property to the ConditionalElement itself that would cause it to re-render. Maybe returning null from the render() function if the property is a certain value? The problem is I don't think this would reset the ConditionalElement itself, but likely all children.

const ParentElement = ({ isElementShown }) => {
  return (
      <ConditionalWrapper isShown={true}>
        <ConditionalElement />
      <ConditionalWrapper/>
    }
  );
}

const ConditionalWrapper = ({ children, isShown }) = {
  // Don't render any children if the element should be hidden
  // Also, styles would be applied to hide the wrapper if necessary
  if (!isShown) {
    return null;
  }

  // Render the children if not hidden
  return children;
};

The second way seems a bit less "hacky" but a bit more nested as well. Would it be recommended anyway? The nice thing is that both ways are capable of either "toggling" the element (un/re-mounting) and "hiding" it (display: none;) if the local state should be kept. This would simply require another parameter (isToggled and isShown).

Upvotes: 1

Views: 2001

Answers (1)

rstar
rstar

Reputation: 1006

Your second approach is ok IMO, however, if you don't like the result of being more nested, you may create a higher order component (HOC) to enhance a component's behavior, the enhanced component works like the original, just that it's aware of, say, a 'shouldRender' property that switches the underlying component between and null.

const toTogglable = (Elem) => (props) =>
  props.shouldRender ? <Elem {...props}/> : null;

const TogglableA = toTogglable(A);
// Then you can use it like
// <TogglableA shouldRender={true} {...otherProps} />

Another advantage of this approach is that you get composibility for free, say if you want to generalize the behavior of show/hide through a property 'shouldHide', you can create another HOC, then just compose them to enhance your component, the result is always one flat component.

Check http://codepen.io/anon/pen/MmzpdG for a working example, based on yours.

Upvotes: 2

Related Questions