3

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?

dwilbank
  • 2,470
  • 2
  • 26
  • 37

1 Answers1

5

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} />
  }
}
Tharaka Wijebandara
  • 7,955
  • 1
  • 28
  • 49
  • Thanks sir for making my structure better. It all works now. The original problem - how to get access to the existing map, was solved by using this.map = map – dwilbank Jun 03 '17 at 16:15
  • @dwilbank I'm glad that it helped you – Tharaka Wijebandara Jun 03 '17 at 16:20
  • Sorry to be dense, but could one of you (@dwilbank or @tharaka-wijebandara) post what the solution looks like? The part I'm least clear on is what goes in the render return. Is it supposed to be empty? (Then maybe remove the commented out stuff in the original question.) Is Snap really supposed to find it using its id? Wouldn't a ref be better? – Sigfried Aug 17 '17 at 12:15
  • @Sigfried thanks for the example. I made it simpler to understand in the context of this question. Yeah. `ref` will be better. I wasn't aware of that `Snap` also accepts dom elements instead of selector strings. – Tharaka Wijebandara Aug 17 '17 at 16:50
  • Great. Definitely easier to understand than my version. – Sigfried Aug 17 '17 at 16:55