Search code examples
reactjssvgsnap.svg

Snap.svg in React - how to access the svg in a lifecycle method?


I have an svg map component which I want to update whenever the props change. Adding and subtract classes on some of the paths... that sort of thing.

Snap.svg seemed to be the way to go. If someone knows a better way I'd like to hear it!

So here's my render method, and the two lines marked with frowns are the ones I want to get working in a lifecycle method such as ComponentWillReceiveProps

render() {
    var map = Snap('#map');
    Snap.load("images/map.svg", function(data){
        if (map) {
            map.append(data);
            const a2047 = map.select('#a2047');               <---- :(
            a2047.attr({stroke:'yellow', strokeWidth:'6px'})  <---- :(
        }
    })

    return (
        <div className="map" id="map"/>
    );
}

The problem is, map won't work anywhere else except inside this Snap.load callback. I tried several ways, using state, window.map, this.map... and I get errors such as 'select is not a function'.

How to get access to the map in ComponentWillReceiveProps ?

Or is Snap.svg even the way to go for this application?


Solution

  • You are doing a direct DOM manipulation with Snap.svg and neither render nor componentWillReceiveProps is a good place to do that. I recommend you to do that in componentDidUpdate which calls immediately after component gets rendered. But this will not be invoked for the initial render. So we have to do this DOM manipulation in both componentDidUpdate and componentDidMount. To prevent repeating the same code, you can keep this operation in another common class method and call it from both componentDidUpdate and componentDidMount. Also, since your props have been updated at this point you can simply access them with this.props inside the new class method.

    Example:

    // @sigfried added to answer his comment below
    import Snap from 'snapsvg-cjs';
    
    export default class Mermaid extends Component {
      svgRender() {
        let element = Snap(this.svgDiv)
        Snap.load("images/map.svg", function(data){
          if (element) {
            element.append(data);
          }
        });
      }
      componentDidMount() {
        this.svgRender();
      }
      componentDidUpdate() {
        this.svgRender();
      }
      render() {
        return  <div ref={d=>this.svgDiv=d} />
      }
    }