React: SVG sprite icons blink when DOM re-renders

Created on 11 Sep 2019  路  5Comments  路  Source: facebook/react

Do you want to request a feature or report a bug?
Bug.

What is the current behavior?
I update state of chechbox from parent and after setState executed on parent all tree starts to re-render and icons blink.

What is the expected behavior?
I update state of chechbox from parent and expect re-render only for checked checkbox.

Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?
I use latest version of create-react app.

So, this is my first react app and I dont like how webpack and react works with SVG. I want to create one static svg symbol sprite and render all icons by their name, which I pass from props.

import React from 'react';
import PropTypes from 'prop-types'; 
class Icon extends React.Component {
    getSprite() {
        let icon = require(`../../assets/icons/sprite.svg`);
        return icon;
    }
    render() {
        return <svg className={`icon ${this.props.className ? this.props.className : ''}`} viewBox="0 0 18 18" width="24">
            <use xlinkHref={`${this.getSprite()}#${this.props.name}`} />
        </svg>
    }
}
Icon.propTypes = {
    name: PropTypes.string.isRequired,
};
export default Icon

Sprite has rendered by gulp task cause with webpack its too difficult and webpack cant modify svg like I need. So, I start to write Ui kit for project - create UiKit component and add some states for children.

class Uikit extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      checkboxes: {
        first: {
          value: true
        },
        second: {
          value: false
        },
        third: {
          value: false
        }
      },
      radio: {
        first: {
          value: true
        },
        second: {
          value: false
        },
        third: {
          value: false
        }
      }
    }
    this.updateCheckboxState = this.updateCheckboxState.bind(this);
  }

  updateCheckboxState(argKey, value) {
    this.setState((state) => {
        state.checkboxes[argKey].value = value;
        return Object.assign({}, state);
    })
  }

  render() {
    function UiWrapper (props) {
      return <div className='p-5 border border-dashed border-ui-blue'>{props.children}</div>
    }
    return <div className='kit-grid'>
      <UiWrapper>
        <Button /*narrow*/ /*thin*/ /*secondary*/ /*disabled*/ /*iconed - only for single icon*/>
          <Icon name='plus' className='mr-2 icon--sm stroke-dark'/>
          袩谢褞褋
        </Button>
      </UiWrapper>
      <UiWrapper>
        {Object.keys(this.state.checkboxes).map((key) => {
          return <Checkbox checked={this.state.checkboxes[key].value}
                  key={key} 
                  onChange={this.updateCheckboxState}
                  id={key} label={key}/>
        })}
      </UiWrapper>
    </div>
  }
}

When updateCheckboxState executed all UiKit re-rendered, icons blink and i'm gets angry. Why?

Most helpful comment

@Tevinthuku @esulimenko I tried to repoduce it on clean create-react-app and found a reason of this problem.
I wrapped all new components into UiWrapper, to prevent component sticking. On clean repo, when I render icons with checkbox and update App state react updated DOM correctly. When I add UiWrapper - problem appear. So, I just add PureComponent extending on UiWrapper and all works correctly.

All 5 comments

I tried to updaate setState inners and return only checkboxes. No changes....

  updateCheckboxState(argKey, value) {
    this.setState((state) => {
      let old = {}
        Object.keys(state.checkboxes).forEach(key => {
          if (key !== argKey) old[key] = state.checkboxes[key]
        });
        state.checkboxes[argKey].value = value;
        return Object.assign({}, {checkboxes: {[argKey]: {value}, ...old }});
    })
  }

All component tree re-render, all icons blink

Hi @WebKieth , could you kindly reproduce the issue on codesandbox that way it may be easier to debug the problem you're facing

Try to use PureComponent for Icon class component or turn it to functional component and use memo
https://reactjs.org/docs/react-api.html#reactpurecomponent
https://reactjs.org/docs/react-api.html#reactmemo

import React, { memo } from 'react';
import PropTypes from 'prop-types';
import icon from '../../assets/icons/sprite.svg';

const Icon = () => (
  <svg className={`icon ${props.className ?props.className : ''}`} viewBox="0 0 18 18" width="24">
    <use xlinkHref={`${icon}#${props.name}`} />
  </svg>
);
Icon.propTypes = {
  name: PropTypes.string.isRequired,
};
export default memo(Icon);

@esulimenko I did it and blinks view longer
@Tevinthuku I sets new repo about this problem in the evening, today.

And thanks for helping, I appreciate it.

@Tevinthuku @esulimenko I tried to repoduce it on clean create-react-app and found a reason of this problem.
I wrapped all new components into UiWrapper, to prevent component sticking. On clean repo, when I render icons with checkbox and update App state react updated DOM correctly. When I add UiWrapper - problem appear. So, I just add PureComponent extending on UiWrapper and all works correctly.

Was this page helpful?
0 / 5 - 0 ratings