The Old County
The Old County

Reputation: 109

_React Js one page application

I am looking to create a one page application with ReactJS.

Is it advisable to combine it with angular or it is suitable just on its own? I would like to populate the one page site with sections - adding various features like carousels, sliders, isotope filters ...

<!DOCTYPE html>
<html>
  <head>
    <title>React Js one page</title>

    <script src="https://fb.me/react-with-addons-0.14.7.min.js"></script>
    <script src="https://fb.me/react-dom-0.14.7.min.js"></script>
  </head>

  <body>
    <section>
     One

       <script>
        var HelloMessage = React.createClass({
          render: function() {
            return <div>Hello {this.props.name}</div>;
          }
        });

        ReactDOM.render(<HelloMessage name="Colonel Mustard" />, mountNode);
      </script>

    </section>

    <section>
      Two


      <script>

        var CommentBox = React.createClass({
          render: function() {
            return (
              <div className="commentBox">
                Hello, world! I am a CommentBox.
              </div>
            );
          }
        });
        ReactDOM.render(<CommentBox />, mountNode);
      </script>


    </section>

    <section>
      Three

      <script>
      "use strict";

      var MarkdownEditor = React.createClass({
        displayName: "MarkdownEditor",

        getInitialState: function getInitialState() {
          return { value: 'Type some *markdown* here!' };
        },
        handleChange: function handleChange() {
          this.setState({ value: this.refs.textarea.value });
        },
        rawMarkup: function rawMarkup() {
          return { __html: marked(this.state.value, { sanitize: true }) };
        },
        render: function render() {
          return React.createElement(
            "div",
            { className: "MarkdownEditor" },
            React.createElement(
              "h3",
              null,
              "Input"
            ),
            React.createElement("textarea", {
              onChange: this.handleChange,
              ref: "textarea",
              defaultValue: this.state.value }),
            React.createElement(
              "h3",
              null,
              "Output"
            ),
            React.createElement("div", {
              className: "content",
              dangerouslySetInnerHTML: this.rawMarkup()
            })
          );
        }
      });

      ReactDOM.render(React.createElement(MarkdownEditor, null), mountNode);
      </script>

    </section>


  </body>

</html>

Upvotes: 1

Views: 1165

Answers (3)

Dan Prince
Dan Prince

Reputation: 30009

If you're just starting out with React, I'd highly recommend following Pete Hunt's advice:

You’ll know when you need Flux. If you aren’t sure if you need it, you don’t need it.

The best thing to do is get started with React by itself and manage application state using the local state that comes with each of your components.

When you find that you start having to pass data back up to parent components, then add Flux into the mix and rewrite your stateful components to instead use Flux stores.

We'll look at how to tackle writing a simplified version of the StackOverflow answer component as a React application from the bottom up.

Notice, I said React application, not React component. This is because there's no technical difference. A React application is a big React component made up of lots of smaller ones.

Identify Components

Once you have an interface for your application (anything from wireframes to html/css) you can visually subdivide them to work out how they'll fit together as React components.

There are no hard and fast rules about how exactly you decide what should or should not be it's own component, but you'll get a feeling for it the more times you do it.

  • is <Answer />
    • is <Votes />
    • is <AnswerText />
    • is <AnswerActions />

Because we're building from the bottom up, we'd start by implementing each of the child components and testing that they work alone.

At this point in the development lifecycle we'd just write static markup for each component. There's no need to think about props or state yet.

We can use the stateless component syntax to get started on the components we've identified. Here's an example of how we might write the <Votes /> component.

function Votes() {
  return (
    <div>
      <a>▲</a>
      <strong>0</strong>
      <a>▼</a>
    </div>
  );
}

Of course this doesn't do anything, but it allows us to start composing our components to get a feel for the structure of the application.

We can render this into the DOM to check that it works at any time.

ReactDOM.render(<Votes />, document.getElementById('app'));

Once you'd finished implementing static versions of the other components, you could put them together to create the parent <Answer /> component.

function Answer() {
  return (
    <div>
      <Votes />
      <AnswerText />
      <AnswerActions />
    </div>
  );
}

Design Data Flow

The next thing to do is to figure out how data flows through your application.

At this point we can create some dummy data in the form of an answer object that looks something like this:

{
  "id": 0,
  "votes": 0,
  "text": "This is an answer"
}

Initially we can render the <Answer /> component by passing this answer object to it as a prop.

<Answer answer={answer} />

Now it's that components job to pass down the appropriate data to its children.

Obviously not each child needs all of the data though, so we'll have to decide what data goes where. Let's update our <Answer /> component.

function Answer(props) {
  var answer = props.answer;
  return (
    <div>
      <Votes id={answer.id} count={answer.votes} />
      <AnswerText text={answer.text} />
      <AnswerActions id={answer.id} />
    </div>
  );
}
  • The <Votes /> component needs know the current number of votes and it also needs to know the id of the answer so that it can communicate change to the server.

  • The <AnswerText /> component just renders a block of text, so that's all we need to pass it.

  • Finally, the <AnswerActions /> component renders a list of links that allow the user to perform some action (share, edit, flag) on the answer. This component also needs the answer's id so that it can communicate with the server.

Now we have to update these child components in turn to use these new dynamic values, instead of the static values we used at first. We'll revisit the <Votes /> component to see this happen.

function Votes(props) {
  var urls = {
    upvote: '/api/answers/' + props.id + '/upvote',
    downvote: '/api/answers/' + props.id + '/downvote'
  };

  return (
    <div>
      <a href={urls.upvote}>▲</a>
      <strong>{props.votes}</strong>
      <a href={urls.downvote}>▼</a>
    </div>
  );
}

Now our vote component will make a HTTP request to the appropriate endpoint when we click on the vote buttons, however, we'd rather make this update without reloading and re-rendering the entire application.

Identify Stateful Components

The final piece of the component development process is to identify stateful components. These components have moving parts and data that will change during the lifetime of the application.

Each time the state inside a component changes, the entire component re-renders. We can revisit the wireframes to see which of our components needs to manage changing data.

This application only has one stateful component () and that's `. When we click on one of the arrows, we need to update the number to reflect the new count.

It's the only one of our components that ever needs to re-render.

This means we'll need to upgrade the component to use React's createClass syntax. This allows it to start managing it's own state.

var Votes = React.createClass({
  getInitialState: function() {
    return { votes: this.props.votes }; 
  },
  upvote: function() {
    var newVotes = this.state.votes + 1;

    this.setState({
      votes: newVotes
    });
  },
  downvote: function() {
    var newVotes = this.state.votes - 1;

    this.setState({
      votes: newVotes
    });
  },
  render: function() {
    return (
      <div>
        <a onClick={this.upvote}>▲</a>
        <strong>{this.state.votes}</strong>
        <a onClick={this.downvote}>▼</a>
      </div>
    );
  }
});

I've jumped the gun a bit and implemented the full component, but hopefully you'll get the idea.

  • First we use getInitialState to set up some state to represent the initial number of votes in the component.

  • Next we implement upvote and downvote component methods that update the component state.

  • Finally we re-implement the render method from before, but have the arrows trigger the new component methods, not page requests.

Each time we make a call to setState, React will re-render the component. Hopefully you can see why we put the state in the <Votes /> component and not the <Answer /> component. It would be crazy to re-render the answer text and actions, just because the votes had changed.

Flux It Up

Once we've identified and implemented all of our stateful components, we can start to move their state out into Flux stores.

It's much more likely that a real application would have an <AnswerStore /> than a <VoteStore />, so that's what we'll implement. For now we'll just keep mocking our data.

var AnswerStore = {
  _listeners: [],
  _answers: {
    "0": {
      "id": 0,
      "votes": 0,
      "text": "This is an answer"
    }
  },
  get: function(id) {
    return this._answers[id];
  },
  update: function(id, update) {
    var answer = this.get(id);
    var updatedAnswer = Object.assign({}, answer, update);
    this._answers[id] = updatedAnswer;
    this.emit();
  },
  listen: function(f) {
    this._listeners.push(f);
  },
  emit: function() {
    this._listeners.forEach(function(f) {
      f();
    });
  }
};

In this example, I've written a fairly generic store that contains data, provides simple handlers for listening to model changes and finally exposes methods for mutating the data in the store.

It's important that our update method treats the individual answers as immutable in this application, otherwise we risk mutating an object that other parts of the application have a reference to, causing the object to change underneath them. We use Object.assign to create a new object each time, based on the old one.

The next thing we need to do is set up some action handlers for this store.

dispatcher.register(function(action) {
  switch(action.type) {
    case 'UPVOTE':
      var votes = ActionStore.get(action.id);
      ActionStore.update(action.id, { votes: votes + 1 });
      break;
    case 'DOWNVOTE':
      var votes = ActionStore.get(action.id);
      ActionStore.update(action.id, { votes: votes - 1 });
      break;
  }
});

This simply wires the update method to two actions called 'UPVOTE' and 'DOWNVOTE'

Now we connect Flux to our <AnswerComponent /> which needs to be re-written in the long form.

var Answer = React.createClass({
  getInitialState: function() {
    return { answer: {} };
  },
  componentWillMount: function() {
    this.update();
    AnswerStore.listen(this.update);
  },
  update: function() {
    var id = this.props.id;
    this.setState({ answer: AnswerStore.get(id) });
  },
  render: function() {
    var answer = this.state.answer;
    return (
      <div>
        <Votes id={answer.id} count={answer.votes} />
        <AnswerText text={answer.text} />
        <AnswerActions id={answer.id} />
      </div>
    );
  }
});

In our componentWillMount method we fetch our initial data for the store, then set up a listener on the store that fetches and updates the component state, whenever the store changes.

Finally, we need a way to dispatch the appropriate actions from our <Votes /> component.

The most popular way to do this is with action creators. An action creator is a function which takes some data as parameters, then packages it up and dispatches it as an action.

 var Actions = {
   upvote: function(id) {
     dispatcher.dispatch({
       type: 'UPVOTE',
       id: id
     });
   },
   downvote: function(id) {
     dispatcher.dispatch({
       type: 'DOWNVOTE',
       id: id
     });
   }
 };

Then we call these actions from inside our <Votes /> component (which can become stateless again).

function Votes(props) {
  var id = props.id;

  return (
    <div>
      <a onClick={Actions.upvote.bind(null, id)}>▲</a>
      <strong>{props.votes}</strong>
      <a onClick={Actions.downvote.bind(null, id)}>▼</a>
    </div>
  );
}

This component now uses the action creators to dispatch actions for our Flux store(s) to handle.

If we look at the flow of data through our application, we can see that we now have a unidirectional cycle, rather than a tree.

  • The <Answer /> component passes the id down to the <Votes /> component.
  • The <Votes /> component dispatches an action using that id.
  • The AnswerStore processes the action and emits a change.
  • The <Answer /> component hears the update and updates its state, re-rendering its children.

Here's a jsfiddle of this demo application.

Scale Up

This is a very simple component that only handles a tiny amount of data flow and even less application state, however, it's enough to show you how to compose a React component and that's all you need to build a React app.

Let's imagine we were implementing a StackOverflow question as a React application.

function App() {
  return (
    <Page>
      <Navigation />
      <SideBar>
        <MetaDetails />
        <Ads />
        <RelatedQuestions />
      </SideBar>
      <Question />
      <AnswerList />
      <AnswerEditor />
      <Footer />
    </Page>
  );
}

It might seem like a complex application, but you can break it down and express it as distinct components, then you can implement and test the components individually, just like we did here and bring them altogether to form a complex application.

Don't Over Complicate

For most simple React applications like this one, Flux is not actually necessary. It's worth remembering that React was released over a year before Flux and yet it was adopted by a lot of developers regardless.

Of course, I've only really covered structuring and implementing components here. Taking an application from wireframes to deployment is a much more complicated process and there's no way it could be covered in detail in one answer. In time, you'll probably also want to learn about:

  • Package management and dependencies
  • Module bundlers
  • Routing
  • Hot Reloading
  • ES6/Babel
  • Redux
  • Server Side Rendering
  • Immutable Data
  • Inline Styles
  • Relay/Falcor/GraphQL

It takes some time to get through this list of things, the trick is not to rush. Don't overcomplicate your existing project until you find the reasons that these solutions exist, naturally.

I think react-howto is the best guide out there. Although it's not heavy on detail, it links to a lot of good resources and most importantly it provides an opinionated guide to the order in which you should learn these technologies on your way to becoming a competent ReactJS developer.

Upvotes: 6

Jake Haller-Roby
Jake Haller-Roby

Reputation: 6427

While you certainly could combine Angular and React, I'm not sure why you would want to, and it's not going to be the easiest of tasks. It's doable, of course, but it's going to provide a lot of difficulty for very little ultimate gain.

If you want to build a SPA with React, I would focus more on finding a Flux implementation that you like, and learning how to integrate that. Flux is designed specifically with React in mind to handle large SPAs which might have complicated data flow, and it's certainly a tool that is easier to incorporate early on.

The only other library I would consider out of the gate would be Immutable.js, which pairs very well with both React and Flux.

But otherwise, until you find a need to bring in additional frameworks/libraries, attempt to hold off. With all of the exciting JS frameworks out there, it's tempting to want to use them all, but in reality you're better off picking one to focus on, and then maybe bringing in some tools from another later on when they are necessitated.

Upvotes: 0

Geoff Wright
Geoff Wright

Reputation: 188

The choice of framework (Angular/React) does not prevent building any of the functionality you described, and your site could be built with neither, either, or both of those frameworks.

Upvotes: 0

Related Questions