react-xml-renderer v1.0.0
react-xml-renderer
this is a small react renderer which allows you to render arbitrary XML using JSX.
usage
import renderXML, { XML, xmlElement } from 'react-xml-renderer';
// you can create XML elements using the xmlElement helper:
const Foo = xmlElement('foo');
const fooBarJsx = <Foo asdf="123">bar</Foo>;
// or use the XML proxy instead:
const fooBarJsx2 = <XML.foo asdf="123">bar</XML.foo>;
// render your JSX to an xml string:
const fooBar = renderXML(fooBarJsx);
const fooBar2 = renderXml(fooBarJsx2);
console.assert(fooBar === fooBar2); // truewhy
if you've ever thought of rendering XML with React, you might have realized you can use react-dom to render your JSX to a string instead of into a dom node:
import React from 'react';
import ReactDomServer from 'react-dom/server';
const jsx = <p>I'm a string!</p>;
const xml = ReactDomServer.renderToStaticMarkup(jsx);
console.log(xml); // <p>I'm a string!</p>okay, cool. now let's start writing some non-html content like, say, an RSS feed:
import React from 'react';
import ReactDomServer from 'react-dom/server';
const jsx = <rss version="2.0"></rss>;
//                             ~~~~~~
// error TS2339: Property 'rss' does not exist on type 'JSX.IntrinsicElements'.
const xml = ReactDomServer.renderToStaticMarkup(jsx);
console.log(xml);oops - typescript expects all lowercase components to be real HTML elements, as defined by JSX.IntrinsicElements from @types/react.
we can work around this by defining our own react component:
import React from 'react';
import ReactDomServer from 'react-dom/server';
const Rss = (props: Record<string, any>) => React.createElement('rss', props);
const jsx = <Rss version="2.0"></Rss>;
const xml = ReactDomServer.renderToStaticMarkup(jsx);
console.log(xml); // <rss version="2.0"></rss>great, now we can build out the rest of our xml using this approach:
import React from 'react';
import ReactDomServer from 'react-dom/server';
const xmlElement = (name: string) => (props: Record<string, any>) =>
	React.createElement(name, props);
const Rss = xmlElement('rss');
const Channel = xmlElement('channel');
const Title = xmlElement('title');
const Description = xmlElement('description');
const Link = xmlElement('link');
const Copyright = xmlElement('copyright');
const LastBuildDate = xmlElement('lastBuildDate');
const PubDate = xmlElement('pubDate');
const Ttl = xmlElement('ttl');
const Item = xmlElement('item');
const Guid = xmlElement('guid');
const jsx = (
	<Rss version="2.0">
		<Channel>
			<>
				<Title>RSS Title</Title>
				<Description>This is an example of an RSS feed</Description>
				<Link>http://www.example.com/main.html</Link>
				<Copyright>2020 Example.com All rights reserved</Copyright>
				<LastBuildDate>Mon, 06 Sep 2010 00:01:00 +0000 </LastBuildDate>
				<PubDate>Sun, 06 Sep 2009 16:20:00 +0000</PubDate>
				<Ttl>1800</Ttl>
				<Item>
					<Title>Example entry</Title>
					<Description>
						Here is some text containing an interesting Description.
					</Description>
					<Link>http://www.example.com/blog/post/1</Link>
					<Guid isPermaLink="false">
						7bd204c6-1655-4c27-aeee-53f933c5395f
					</Guid>
					<PubDate>Sun, 06 Sep 2009 16:20:00 +0000</PubDate>
				</Item>
			</>
		</Channel>
	</Rss>
);
const xml = ReactDomServer.renderToStaticMarkup(jsx);
console.log(xml);this code doesn't trigger any typescript type errors! however, if we run it, we get a runtime error:
Error: link is a void element tag and must neither have `children` nor use `dangerouslySetInnerHTML`.<link>, in HTML, is a void element, meaning it can't accept children.
this shouldn't matter to us, because we're not writing HTML, we're writing XML. but react-dom has special checks to prevent us from writing invalid HTML which can't be disabled. if we want to create non-HTML from JSX, we need a different renderer.
prior art
react-xml-renderer is a stripped-down fork of react-tiny-dom, and uses jsdom behind-the-scenes.