React-grid-layout: [Bug] calculateWidth is not updating the parent offsetWidth correctly

Created on 23 May 2018  路  8Comments  路  Source: STRML/react-grid-layout

On initial page load or browser resize offsetWidth is correct, however if there is a change to the DOM which adjusts the parent width it is not correct.

I started with my sidebar expanded and the parent offsetWidth at 1298. When I collapse my sidebar the onWindowResize function is called and the offsetWidth now shows 1148 but ReactDOM.findDOMNode(this) isn't pulling the latest.

Steps To Reproduce

  1. Load window initially
  2. Use another element sampleSidebar resize trigger $(window).resize();
  3. Adjust sampleSidebar which causes parent to change width rather than browser resize
  4. Notice that onWindowResize is called but node.clientWidth is previous size.

SampleSidebar.jsx

onSidebarToggle = () => {
    ...code that toggles size

    $(window).resize();
}

WidthProvider.jsx

onWindowResize = () => {
    this.calculateWidth()
}

calculateWidth = () => {
    if (!this.mounted) return
    let node = ReactDOM.findDOMNode(this);

    console.log(`clientWidth: ${node.clientWidth}`);
    console.log(node);

    if (node instanceof HTMLElement) {
        this.setState({ width: node.offsetWidth })
    }
}

alt text

FYI the link to WebpackBin Template isn't working.

All 8 comments

I was able to find a way around this similar to what is mentioned in #607 and #608. The issue is that I'm using a short animation on my element resizing and calculateWidth is called immediately. I solved this by adding a very short timeout.

onWindowResize = () => {
    setTimeout(() => {
        this.calculateWidth()
    }, 200)
}

If I submit a PR for this fix, would it be possible to update WidthProvider out of the box to accept a timeout? I would imagine that a lot of people use animations.

Tried an initial stab at a PR but I'm running into the following error.

Error: lib/components/WidthProvider.jsx:68
 68:       }, delay)
              ^^^^^ delay. Could not resolve name

Here is my updated WidthProvider.jsx

// @flow
import React from "react";
import PropTypes from "prop-types";
import ReactDOM from "react-dom";
import type { ComponentType as ReactComponentType } from "react";

type WPProps = {
  className?: string,
  measureBeforeMount: boolean,
  delay: number,
  style?: Object
};

type WPState = {
  width: number
};

/*
 * A simple HOC that provides facility for listening to container resizes.
 */
export default function WidthProvider<
  Props,
  ComposedProps: { ...Props, ...WPProps }
>(
  ComposedComponent: ReactComponentType<Props>
): ReactComponentType<ComposedProps> {
  return class WidthProvider extends React.Component<ComposedProps, WPState> {
    static defaultProps = {
      measureBeforeMount: false,
      delay: 0
    };

    static propTypes = {
      // If true, will not render children until mounted. Useful for getting the exact width before
      // rendering, to prevent any unsightly resizing.
      measureBeforeMount: PropTypes.bool,
      delay: PropTypes.number
    };

    state = {
      width: 1280
    };

    mounted: boolean = false;

    componentDidMount() {
      this.mounted = true;

      window.addEventListener("resize", this.onWindowResize);
      // Call to properly set the breakpoint and resize the elements.
      // Note that if you're doing a full-width element, this can get a little wonky if a scrollbar
      // appears because of the grid. In that case, fire your own resize event, or set `overflow: scroll` on your body.
      this.onWindowResize();
    }

    componentWillUnmount() {
      this.mounted = false;
      window.removeEventListener("resize", this.onWindowResize);
    }

    onWindowResize = () => {
      setTimeout(() => {
        if (!this.mounted) return;
        // eslint-disable-next-line
        const node = ReactDOM.findDOMNode(this); // Flow casts this to Text | Element
        if (node instanceof HTMLElement)
          this.setState({ width: node.offsetWidth });
      }, delay)
    };

    render() {
      const { measureBeforeMount, delay, ...rest } = this.props;
      if (measureBeforeMount && !this.mounted) {
        return (
          <div className={this.props.className} style={this.props.style} />
        );
      }

      return <ComposedComponent {...rest} {...this.state} />;
    }
  };
}

@CWSites I have a issue like yours.

    let event = new CustomEvent("resize");
    window.dispatchEvent(event);

I add above code into my relate component.

@linmodev did that fix the error? Where in the code did you add that to?

  componentDidUpdate() {
    let event = new CustomEvent("resize");
    window.dispatchEvent(event);
  }

This is not a nice solution. But I solve the item component is out of parent's range.
@CWSites

@linmodev why don't you submit a PR with this change? It seems much more straight forward than mine if it works.

@CWSites But these code is not insert component source code, I just add this code to some place it needs.So it not a real final solution.

I'm having the exact same issue, and even in a manual window resize, the width value in widthProvider is not updated.
Any new thoughts ?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

lantzh picture lantzh  路  3Comments

Francismb picture Francismb  路  4Comments

gerahimanshu picture gerahimanshu  路  4Comments

larrydahooster picture larrydahooster  路  3Comments

sasha240100 picture sasha240100  路  3Comments