Giter VIP home page Giter VIP logo

isomorphic-fluxxor-experiment's Introduction

Isomorphic Fluxxor

This is an application designed to test rendering an isomorphic React app powered by Fluxxor. It fetches some basic information from Reddit.

Running

Requires Node.js installed.

$ npm install
$ npm start

You can set a different port with the PORT environment variable.

Overview

Here's how it works from a high level.

Note: As a proof-of-concept, this app cuts some corners, skips some error checking, and short-circuits some best practices. Consult the docs for React, react-router, and Fluxxor for more information on the proper use of each.

  1. Components don't fetch data by dispatching actions; instead, they ask for it directly from the appropriate store using a getter on that store.

    getStateFromFlux() {
      return {
        subredditData: subredditStore.getSubreddit(subreddit)
      };
    },
  2. If the store has the data cached, it returns it immediately. Otherwise, it returns a "loading token" and starts an async fetch for the data.

    getSubreddit(subreddit) {
      if (this.state.subreddits[subreddit]) {
        return this.state.subreddits[subreddit];
      } else {
        this.state.subreddits[subreddit] = LOADING_TOKEN;
        this.reddit.getSubreddit(subreddit, (err, data) => {
          // ...
        });
        return LOADING_TOKEN;
      }
    },
  3. When the async fetch is complete, the store dispatches a "success" action to inform the system that the data is ready. It uses this action to update itself.

    getSubreddit(subreddit) {
      // ...
    
        this.reddit.getSubreddit(subreddit, (err, data) => {
          if (err) dispatch(FETCH_FAILURE, {subreddit: subreddit, err: err});
          else     dispatch(FETCH_SUCCESS, {subreddit: subreddit, data: data});
        });
    
      // ...
    },
    
    handleFetchSuccess(payload) {
      this.state.subreddits[payload.subreddit] = payload.data;
      this.emit("change");
    }
  4. The component that originally requested the data uses the loading token to determine if the data is ready or not.

    javascript render() { return (

    /r/{this.state.name}

    { this.state.subredditData === SubredditStore.LOADING_TOKEN ? this.renderLoadingMessage() : this.renderSubredditData() }
    ); },

    
    
  5. On the server, we inject a server-side version of the Reddit API into our store.

    var reddit = new Reddit(ServerRedditFetcher);
    var flux = Flux(reddit);

    This object emits events whenever it starts to fetch or finishes fetching a request from the Reddit API. We can use this information to know when we're done loading data asynchronously (because the the number of requests started minus the number of requests finished will be zero).

    reddit.on("reqs", (num) => {
      if (num === 0) {
        reddit.removeAllListeners();
        render();
      }
    });
  6. We do an initial server-side render to kick off the getInitialState calls, which start the async data requests flowing. Note that we don't do anything with the return value; we're only interested in the side effects that rendering the app has on our stores.

    Router.run(Routes, req.url, (Handler, state) => {
      React.renderToString(
        <Handler key={state.path} flux={flux} />
      );
    });
  7. Once the Reddit API finishes the last request, we call render() (see above); here, we render the app again, and this time we send the React HTML and the serialized Fluxxor store data to the client.

    var render = () => {
      var serializedFlux = flux.serialize();
      Router.run(Routes, req.url, (Handler, state) => {
        var content = React.renderToString(
          <Handler key={state.path} flux={flux} />
        );
    
        res.render("index", {
          reactMarkup: content,
          serializedFlux: serializedFlux
        });
      });
    };
  8. Our server-side view injects the HTML and serialized data into the page.

    <script>
    window.fluxData = <%- serializedFlux %>;
    </script>
    <div id="app-container"><%- reactMarkup %></div>
  9. When the client-side application boots, we use the serialized store data to reconstruct the state from the server. We also use a different version of the Reddit API that works on the client.

    var reddit = new Reddit(ClientRedditFetcher);
    
    var flux = Flux(reddit);
    if (window.fluxData) {
      flux.hydrate(window.fluxData);
    }
    
    Router.run(Routes, Router.HistoryLocation, (Handler, state) => {
      React.render(
        <Handler key={state.path} flux={flux} />,
        document.getElementById("app-container")
      );
    });
  10. Since the flux state is the same on the client as it was on the server, React transparently "upgrades" our static markup into a proper single-page app. Any future asynchronous fetches are handled the same way they were on the server--by calling the getter on the store, which triggers an async fetch and an action as a result.

Considerations

This seems to work well, and I like the declarative approach to getting appropriate component state from the stores. I haven't investigated the performance characteristics of rendering twice on the server. Additional care needs to be taken to ensure we don't leak if something goes wrong.

One situation where this naive approach will not work is when the availability of asynchronous data causes the second server-side render to include new components not included in the first render, and these new components also request async data from the stores. In such a case, you would need to repeat the loop that checks for outstanding requests until there are no more. An alternative approach, which I think I like, is to limit the fetching of async data to components that will be used by react-router as route handlers (thus ensuring they are triggered on the first server-side render).

isomorphic-fluxxor-experiment's People

Watchers

 avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.