React.js FeedWidget

written in react.js, tutorial

Adding a “Recent Posts” widget to my site mwil.so using React.js

After setting up this blog, I wanted to add a link to my personal website. I felt it was a good opportunity to make a simple “Feed Widget” using React.js, that could be reused by others.

What’s React.js?

React.js is a javascript library, created by FaceBook, that allows developers to build reusable user interface components. One good example is a comments box similar to the Disqus comments at the bottom of this page. The sample tutorial on the react site takes you through creating a comment box of your own.

I like using React because it allows you to create self contained components that take care of updating and rendering themselves. I have used it extensively on a site I’m working on for the navigation bar, footer, video and audio players, and I feel it has allowed me to make components with a lot of dynamic behaviour with no issues.

FeedWidget

FeedWidget is the unimaginative name I’ve given to the component I’ve created for displaying posts from an rss or atom feed on a webpage, it can be seen in action at the bottom my mwil.so site.

Built using React.js, it fetches the atom feed from this blog, parses the contents, and displays them as html. It’s simple enough to be a good introduction into using React.

Setting Up

Download the React starter kit here and copy the build/react.js and build/JSXTransformer.js files into your project directory. Then create an index.html page with the following contents:

<!DOCTYPE html>
<html>
  <head>
    <script src="build/react.js"></script>
    <script src="build/JSXTransformer.js"></script>
  </head>
  <body>
    <div id="example"></div>
    <script type="text/jsx">
      React.render(
        <h1>Hello, world!</h1>,
        document.getElementById('example')
      );
    </script>
 </body>
</html>

Loading the page will cause ‘Hello, World!’ to be rendered as an h1 element in the example div.

Fetching Posts

To retrieve and parse the posts from the atom.xml I used a jQuery plugin called JFeed The repository hasn’t been updated in 4 years and I had to make a few simple tweeks to get it to work with the latest version jQuery. My tweeked version can be found in the FeedWidget repo here

Once you have added it to your project, retrieving posts is a small bit of code:

jQuery.getFeed({
  url: 'http://matthewcodes.github.io/atom.xml',
  success: function(feed) {
    //do something with feed!
  }
});

Using React to render posts

Update your index.html so it looks like this:

<!DOCTYPE html>
<html>
  <head>
    <script src="http://code.jquery.com/jquery-1.11.2.min.js"></script>
    <script src="js/jquery.jfeed.js"></script>
    <script src="js/react.js"></script>
    <script src="js/JSXTransformer.js"></script>
  </head>
  <body>
    <div id="feed"></div>
    <script type="text/jsx" src="js/FeedWidget.js"></script>
  </body>
</html>

Notice we have added a new script import to the bottom called FeedWidget.js, create a blank file called FeedWidget.js in your js folder.

Add the following content to the FeedWidget file:

var LiveAlert = React.createClass({
  render : function() {
    return (<div><p>Loading Posts</p></div>);
  }
});

React.render(
  <LiveAlert/>,
  document.getElementById('feed')
);

If you load up your index.html file you will see the message “Loading Posts” rendered on the page.

To fetch posts we can add our jFeed call to one of the Lifecycle events componentDidMount this gets “Invoked once, only on the client (not on the server), immediately after the initial rendering occurs.” So it is a perfect place to add our ajax call:

var LiveAlert = React.createClass({
  getInitialState: function() {
  return {
       data : undefined
    };
  },
  componentDidMount: function() {
    var widget = this;
    jQuery.getFeed({
      url: this.props.url,
      success: function(feed) {
        if(widget.isMounted()) {
          widget.setState({
            data: feed
          });
        }
      }
    });
  },
  render : function() {
     ...
  }
});

Notice in the componentDidMount function we have added our getFeed call, and in the success function we call widget.setState, when the setState method is called it updates the state object and causes react to re-render the component.

Also notice I have added a getInitialState function, this is used by react to set up the default state of the component, although it’s not really necessary in this example.

You can also see that the url we are calling to fetch the posts is set as a property: this.props.url, this is passed in when we render the component like so:

React.render(
  <LiveAlert url="http://matthewcodes.github.io/atom.xml" lengthOfExcerpt="78"/>,
  document.getElementById('feed')
);

I have also added a lengthOfExcerpt property that we will use later.

At this point our component is fetching the posts and storing them in the component’s state, the next step is to render the posts, and we do this in the render function.

Add the following code to replace the render function:

removeCdata : function(text) {
  return text.replace("<![CDATA[", "").replace("]]>","").trim();
},
render : function() {

  if(this.state.data) {

    var widget = this;

    var items = this.state.data.items.map(function(item) {
        var title = widget.removeCdata(item.title);
        var description = widget.removeCdata(item.description);
        description = description.substring(0, widget.props.lengthOfExcerpt);

        return (
          <div>
            <a href={item.link}><h1>{title}</h1></a>
            <p>{description}</p>
          </div>
        );
    });

    return(
      <div>
        {items}
      </div>
    );

  } else {
    return (<div><p>Loading Posts</p></div>);
  }
}

Most of the code here is self explanatory, but the most important part to understand is what we return from this function is what gets rendered on the page, so from the code above:

var items = this.state.data.items.map(function(item) {
    var title = widget.removeCdata(item.title);
    var description = widget.removeCdata(item.description);
    description = description.substring(0, widget.props.lengthOfExcerpt);

    return (
      <div>
        <a href={item.link}><h1>{title}</h1></a>
        <p>{description}</p>
      </div>
    );
});

return(
  <div>
    {items}
  </div>
);

Returns a div containing the all the items, which have been stored in the items variable.

I have intentionally avoided adding any styling to the widget so that others can customise it for their sites. On my own site I use bootstrap to take care of the styling for me.

If you want to see the complete source code for the widget it is available on GitHub and a working example can be viewed here.


Comments