Passing RSS feed data from server to client without MongoDB


#1

Help a newbie out!

I posted this as a question to an old topic but got no answer yet, so I thought why not make it a topic. I have looked and looked but haven’t found a tutorial or an example of actually reading data from an RSS feed to the client.

I’m using parse-feed to parse an RSS feed on the server side and that works well. However, being quite new to Meteor and Javascript in general, I can’t figure out how to display the feed in the client.

What I’m trying to accomplish is to display my latest blog posts from Medium on my personal website (under development). It would be easy with a normal pub/sub, but I don’t want to insert the posts to Mongo since they won’t be needed when newer posts come and replace them. This wouldn’t be a problem since the overall number of posts won’t ever be huge, but there has to be another way.

What I’ve tried is having a server side method that fetches and parses the feed:

fetchLatestBlogposts() {
    let feed = require("feed-read");
    let url = "https://www.medium.com/feed/[myusernamehere]";
    let firstPosts = [];

    feed(url, function(err, posts) {
        if (err) {
            console.log("There has been an error: " + err);
        }

        for(let i = 0; i < 2; i++) {
            firstPosts.push({
                "title": posts[i].title,
                "content": posts[i].content,
                "published": posts[i].published,
                "link": posts[i].link
            });
        }
        console.log("hello from server" +firstPosts);
        return firstPosts;
    });
}

This method successfully fetches the blogposts and inserts them to the array (which the server side console.log proves) I would like to return. I can’t however get them through to the client.

My client side looks somewhat like this. I’m using React as the UI component, so there are eg no Blaze templates.

posts() {
    Meteor.call("fetchLatestBlogposts", function(err, result) {
        if (err) {
            console.log("error in client: " + err);
        }
        if (result) {
            return result;
        }
        console.log("no result found");
    });
}


render() {
    return(
        <div className="blog-latest">
            <div className="blog-latest-text">
                //irrelevant code until next line
                {console.log("UI " + this.posts())}
            </div>
        </div>
    )
}

The client logs “UI undefined” (from render()) and “no results found” (from posts()) respectively. The server side method does trigger and logs what it’s supposed to log to the server console. So it seems apparent that the render() finishes before posts(), but I can’t figure out why posts() doesn’t get any result from the callback.

I suppose my problem is rather with not quite understanding the asynchronous way that Javascript works than with Meteor, but any help is greatly appreciated.


#2

What is the output of that?

I think that feed() method is async. So I think that’s why the result is not returned. You can test that with putting this just before that console log:

Meteor._sleepForMs(5000);

It will delay the method even further. I expect you see “no result found” in your browser console before you see the server method logging: “hello from server” on the server.

But you will need to test that. If that is actually the case here is how to solve it:

So you return the promise and that will make everything wait until the result is there. If you keep that sleepForMs call in it you will clearly see what happens.

If it’s a different issue let me know what you have found!


#3

Hi and thanks for your answer, I’m sorry I haven’t had the time to reply earlier, other work has kept me really busy.

Nevertheless I got it working like I wanted to so huge thanks!

The original issue was not only that the server side method returned before executing the async feed-function but also that the client was built so that it wouldn’t re-render even when receiving the asynchronously fetched data.

I realized I had a fundamental error in the way I was using React and not so much with Meteor itself. I don’t know how I was assuming the component to re-render after receiving the async result, because the way I had structured my template didn’t allow that.

In short, now I have the result stored in the component’s state and componentDidMount() handling the re-render.

So the client looks like this (for the relevant part):

export default class BlogLatest extends TrackerReact(Component) {
    constructor() {
        super();
        this.state = {
            posts: []
        };
    }

    componentDidMount() {
        let self = this;
        this.posts(self);
    }

    posts(self) {
        Meteor.call("fetchLatestBlogposts", (err, result) => {
            if (err) {
                console.log("error in client: " + err);
            }
            if (result) {
                self.setState({posts: result});
            } else {
                console.log("no result found");
            }
        });
    }

    render() {
        return(
            <div className="blog-latest">
                <div className="blog-latest-text">
                    //irrelevant code until
                    <ul>
                        //basically map the results as <LatestPost/> (it's a <li>) components to a <ul>
                        {this.state.posts.map((post) => {
                            return <LatestPost post={post} key={post.published} className="latest-posts" />
                        })}
                    </ul>
                </div>
            </div>
        )
    }
}

And on the server I needed to use a future (or a promise or async/await would probably do as well) like you said. This is what the server method looks like now:

fetchLatestBlogposts() {
        let feed = require("feed-read");
        let url = "https://www.medium.com/feed/[myUsername]";
        let firstPosts = [];
        let Future = require("fibers/future");
        let future = new Future();

        feed(url, (err, posts) => {
            if (err) {
                console.log("There has been an error: " + err);
            } else {
                for(let i = 0; i < 2; i++) {  
                    firstPosts.push({
                        "title": posts[i].title,
                        "content": posts[i].content,
                        "published": posts[i].published,
                        "link": posts[i].link
                    });
                }
                future.return(firstPosts);
            }
        });

        return future.wait();
}

The only remaining issue is to struggle with the quite horrible pre-formatting that Medium does to its feeds but that’s another story.

Thanks again!