Create an interactive SVG component using React

- 1 answer

Ad

Let's say I have an SVG element with paths for all US states.

<svg>
    <g id="nh">
        <title>New Hampshire</title>
        <path d="m 880.79902,142.42476 0.869,-1.0765 1.09022,..." id="NH" class="state nh" />
    </g>
    ...
</svg>

The SVG data is saved in a separate file with a .svg extension. Say I want to create a React component of that map, with complete control over it so that I can modify the styling of individual states based on some external input.

Using Webpack, as far as I can tell, I have two options for loading the SVG markup: Insert it as raw markup using the raw-loader and create a component using dangerouslySetInnerHTML:

var InlineSvg = React.createClass({
  render() {
    var svg = require('./' + this.props.name + '.svg');
    return <div dangerouslySetInnerHTML={{__html: svg}}></div>;
  }
});

or manually convert the markup to valid JSX:

var NewComponent = React.createClass({
  render: function() {
    return (
        <svg>
            <g id="nh">
                <title>New Hampshire</title>
                <path d="m 880.79902,142.42476 0.869,-1.0765 1.09022,..." id="NH" className="state nh" />
            </g>
            ...
        </svg>
    );
});

Finally, let's say that in addition to the SVG map, there's a simple HTML list of all the states. Whenever a user hovers over a list item, the corresponding SVG path should shift fill color.

Now, what I can't seem to figure out is how to update the React SVG component to reflect the hovered state. Sure, I can reach out into the DOM, select the SVG state by classname and change its color, but that doesn't seem to be the "react" way to do it. A pointing finger would be much appreciated.

PS. I'm using Redux to handle all communication between components.

Ad

Answer

Ad

You need to do two things:

1) Set an event listener on each list item to inform your app of the highlighted item.

<li
    onMouseOver={() => this.handleHover('NH')}
    onMouseOut={() => this.handleUnhover()}
>
    New Hampshire
</li>

2) Capture the data, and propagate it your SVG component.

This is the more complicated part, and it comes down to how you've structured your app.

  • If your entire app is a single React component, then handleHover would simply update the component state
  • If your app is divided into multiple components, then handleHover would trigger a callback passed in as a prop

Let's assume the latter. The component methods might look like this:

handleHover(territory) {
    this.props.onHighlight(territory);
}

handleUnhover() {
    this.props.onHighlight(null);
}

Assuming you have a parent component, which contains both the SVG map and the list, it might look something like this:

class MapWrapper extends React.Component {

    constructor(props) {
        super(props);
        this.state = {
            highlighted: null;
        };
    }

    setHighlight(territory) {
        this.setState({
            highlighted: territory
        });
    }

    render() {
        const highlighted = { this.state };
        return (
            <div>
                <MapDiagram highlighted={highlighted} />
                <TerritoryList onHighlight={(terr) => this.setHighlight(terr)} />
            </div>
        );
    }

}

The key here is the highlighted state variable. Every time a new hover event occurs, highlighted changes in value. This change triggers a re-render, and the new value is passed onto MapDiagram which can then determine which part of the SVG to highlight.

Ad
source: stackoverflow.com
Ad