Using React with SVG

Intro / Purpose

A couple classmates and I recently built a React/Redux app called Constellation Playground, which presents users with a 2-dimensional sky full of stars and allows them to create their own unique constellations. When starting out, we found that using React with Redux to keep track of global state would work well; the sky could automatically update itself each time a user adds or removes a star to the current constellation. Figuring out the best way to display the stars on the screen was a more challenging decision.

We tried Canvas first, but quickly realized that it would be a nightmare to achieve the functionality we were looking for. After looking into 2D and 3D JS frameworks, we eventually found that SVG would be the best option. We could easily create <circle> elements to represent stars and place them at specific coordinates on the SVG plane. The next step would be adding event handlers to the stars. And this is where React comes in.

Rendering an SVG Parent Component

The beauty of React is its ability to seamlessly mount and unmount components. Typically, a component will return a <div> wrapper with JSX rendering HTML elements inside of it. In our case, the parent component returns a full screen<svg> element with JSX inside it. The code looks like this:

return (
<svg width={window.innerWidth} height={window.innerHeight} style={background}>
  // JSX elements
  </svg>
)

Because our full window is now an SVG object, we can’t render typical HTML elements like <p> or <button> etc. Instead, we must render SVG elements like <text> and <rect> . But things get more interesting if we want to, for example, dynamically create child elements with props passed down based on user input or a database call.

Dynamically Rendering SVG Child Components

Now we can create a new component as we normally would. The only difference is that in this case, it must render something that is compatible with SVG.

Let’s use my Constellation Playground app as an example. We have a component representing a star, which we called “SuperStar”. The SuperStar component had its own state, component lifecycle methods, and event handlers for mouse hovers and clicks. Here is what the render() method looks like:

render() {
  var circleStyle = this.state
  return (
<circle onClick={this.handleStarClick.bind(this)} cx={this.props.x} cy={this.props.y} r='3' fill="white" onMouseEnter={this.handleHover.bind(this)} onMouseLeave={this.handleMouseLeave.bind(this)} style={circleStyle}/>
)
} 

First, notice that the only element returned is a <circle>; an SVG-friendly element.

Next, notice that the <circle> gets its x and y coordinates via props. When the app loads, coordinates for 200 stars are retrieved from our database. These are passed down from the parent (“Sky”) component as props to each SuperStar. That is done inside the return of the parent component using the map method:

<svg ... >
  ...
  { this.props.stars.map((star, i) => 
<SuperStar key={i} id={star.id} x={star.x} y={star.y} z= {star.z} />
)}
  ...
</svg>

Voilà! 200 stars in the SVG sky.

Conclusion (TLDR)

You can render React components inside of a parent SVG element as long as they return SVG-friendly elements like <circle> , <rect> , <line>, and <text> . A <div> is invalid inside <svg> </svg>. Check out the SVG documentation for all your SVG needs.

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.