2

I am a new React learner, and I may have a situation with render() and findDOMNode.

What I have and what is the wrong behavior :

I'm quite new to React so, I may have done lots of mistakes there. I have a Webpart that I can change the appearance by applying a theme. This is done by selecting among themes on the PropertyPane. I developped a new theme for that WP. It is displaying well except for one little "bug" : When selecting that special theme on PropertyPane, my WP is not rendered in this theme, and I have to refresh the page to see it applied.

What I developed :

Here are the several parts of my Theme component :

export interface IToolBoxProps extends IItemsView {
  btnSettings?: IButtonSettings;
}
export default class Toolbox extends React.Component<IToolboxProps, {}> {
  constructor(props) {
    super(props);
  }

  public render(): React.ReactElement<IToolboxProps> {
    let result;
    let componentNode = ReactDOM.findDOMNode(this);
    let parentSection: Element;
    if (componentNode) {
      parentSection = this._findAncestor(componentNode, 'CanvasSection');
    }
    result = this.props.items.map((item, index: number) => {
      let stylesWP: string = '';

      if (parentSection.classList.contains('CanvasSection-xl12')) {
        //If Canvas Section is a full 12Col width
        if (index > 1) {
          stylesWP = styles.smallFormat;
        } else if (this.props.items.length === 1) {
          stylesWP = styles.bigFormat;
        }
      } else if (parentSection.classList.contains('CanvasSection-xl6')) {
        //If Canvas Section is 2col width
        if (index >= 1) {
          stylesWP = styles.smallFormat;
        } else if (index === 0) {
          stylesWP = styles.mediumFormat;
        }
      }
      return (
        <div className={styles.toolboxItem + ' ' + stylesWP}>
          <div className={styles.toolboxContent }>
            <span className={styles.toolboxSpanContainer }>
              <a href={item.URL}>{strings.SpanText}</a>
            </span>
            <h2 className={styles.toolboxItemTitle}>
              <a href={item.URL}>{item.Title}</a>
            </h2>
            <p className={styles.toolboxItemResume}>
              <a href={item.URL}>{item.Resume}</a>
            </p>
          </div>
        </div>
      );
    }, this);

    return <div>
      {
        this.props.items.length > 0 &&
          <div className={styles.toolboxContainer}>
            {result}
            </div>
      }
    </div>;
  }

  private _findAncestor(el: Element, className: string): Element {
    while ((el = el.parentElement) && !el.classList.contains(className)) {
      //turn while SP is loading -added this comment because Sonar.
    }
    return el;
  }


}

What is the error thrown in the console : When selecting the checkbox, I have the following error :

Uncaught TypeError : Cannot read property 'classList' of undefined on if (parentSection.classList.contains('CanvasSection-xl12')) {

This means that my parentSection is undefined. That means my componentNode is also undefined. I tried with componentDidMount() and componentWillMount() but had no interesting results. And, moreover, I read somewhere that findDOMNode is no longuer advised, it even is deprecated.

So, How can I get my current webpart's Node when first rendering the webpart depending on the PropertyPane's properties ? (and secondly, how can i retrieve its parent's node, because I know the way here is not conventional..)

Could you please advise me on how i could make my theme render when applying the theme ? because that bug aside, it is rendering perfectly ! Thank you.

Have a nice night

1

Gaelle,

React takes a bit of getting used to when you get started.

One of the things that can make it difficult to learn React is that some of the samples out there can be quite old. For example, findDOMNode is considered deprecated; instead, you should create refs for the elements you want to query/manipulate.

Typically, you'll want to create a private variable to store the element reference in your class, using whatever HTML element type you're expecting:

export default class Toolbox extends React.Component<IToolboxProps, {}> {
    // declare the private variable
    private _myElement: HTMLDivElement = undefined;

Then later in your render code, set the variable to the element's ref:

<div
          ref={(el: HTMLDivElement) => this._myElement= el}>
You've just proved advertising works!
</div>

Then you can manipulate or query the elements (usually in or after componentDidMount) by using the private variables:

if (this._myElement.classList.contains('CanvasSection-xl12')) {
...
}

For example, you can use this logic to define your state:

if (this._myElement.classList.contains('CanvasSection-xl12')) {
    this.setState({
            is12Col: true
        });
}

...and use the state to control your rendering:

public render(): React.ReactElement<IToolboxProps> {
    let result;
    let parentSection: Element;

    result = this.props.items.map((item, index: number) => {
      let stylesWP: string = '';

      if (this.state.is12Col) {
         ...

Using this approach, you'll always be sure that you're only ever manipulating your own elements, and won't have to worry about duplicate element IDs, etc.

I realize that this is a very delayed response, but I hope it'll help someone.

  • 1
    Hi @Hugo ! Thx for the reply ! It's been a long time and a lot of projects has been coded since, so I don't really remember how i got out of this, but refs was definitively not a part, because of the version of the spfx / react . i think it was not react 16 and thus was not working with refs (if i can recall well). But i'll mark your reply as the answer, since it is well explained (thanksa lot for that!) and now everybody has react 16 implemented ! here is for a further read : medium.com/@martin_hotell/… – Gaelle Jun 27 at 14:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.