Skip to content
catmando edited this page Oct 27, 2015 · 1 revision

The full power of react comes from updating state and having the effected components re-render.

States in react act very much like Ruby instance variables, with the added feature that when the state variable changes it causes the component instance to be re-rendered. Like params (react.js props) states in Reactive-Ruby work exactly like in react.js, but again with some added love.

Lets compare the syntax for React.js and Reactive Ruby states.

var CommentBox = React.createClass({
  getInitialState: function() {
    return {data: []};
  },
  render: function() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={this.state.data} />
        <CommentForm />
      </div>
    );
  }
});

and in Ruby

class CommentBox 
  include React::Component
  define_state data: []  # The form define_state :data will initialize to nil
  def render
    div.commentBox do
      h1 { "Comments" }
      CommentList data: data # like params declaring a state creates a instance method for you but
                             # you could still say state[:data] too.
      comment_form  # comment_form is an instance method we will define later on
    end
  end
end

So far not much is going to happen unless we have a way to update that state. Lets poll a server to get the up to date comments:

class CommentBox

  # one nice thing about ruby is we can open our component class and add to it.
  # in this case its a bit artificial, but it can be useful in structuring a large component.

  required_param :url, type: String
  optional_param :poll_interval, default: 60

  after_mount do
    every(poll_interval) do # we use the opal browser utility to call the server every poll_interval seconds  
      HTTP.get(url) do |response|
        if response.ok?               
          data! JSON.parse(response.body)    
        else
          puts "failed with status #{response.status_code}"
        end
      end
    end
  end
end

Every state has a getter method (the state name) and a setter method (the state name followed by a "!"). The setter method can take a parameter (the new state value) and it will update the state, and notify react of the change. THe setter method returns the current value (before updating) which is often handy. Under the hood it uses the react setState function to accomplish its work.

So now we need a way to add a comment to the data base, that will be the work of our comment_form method and its helper submit_comment.

For the purpose of this tutorial we make comment_form as method. In reality you would probably make it a separate component.

class CommentBox

  define_state author: ""
  define_state text: ""

  def comment_form
    div do
      div do
        "Author: ".span          
        input.author_name(type: :text, value: author, placeholder: "Your name", style: {width: "30%"}).
          # and we attach an on_change handler to the input.  As the input changes we simply update author.
          on(:change) { |e| author! e.target.value } 
      end
      
      div do
        # lets have some fun with the text.  Same deal as the author except we will use a text area...
        div(style: {float: :left, width: "50%"}) do
          textarea(value: text, placeholder: "Say something...", style: {width: "90%"}, rows: 30).
            on(:change) { |e| text! e.target.value }
        end
      end   
      button { "Post" }.on(:click) { submit_comment author: author!(""), text: text!("") }
    end
  end

  def submit_comment(comment = {})
     HTTP.post(url, payload: comment) do |response|
      puts "failed with status #{response.status_code}" unless response.ok?
    end
    comments! << comment  # notice the second form of the setter method is being used here
  end

end

A key feature of the above code is the line comments! << comment. Notice that the setter method is not given a parameter. In this form the setter method returns the current value of comments wrapped in an observer which is a unique Reactive Ruby construct. The observer object will act just like the object it wraps, but reports any method calls to the object as state changes to react. Observers have other features as well and together they accomplish a lot of the work of react links, and flux notifiers.

So in the case of comments! << comment what happens is the current value of comments (an array) is returned as an observer, and the array push method is applied. This updates the underlying value of the comments array, but also notifies react that state has changed. The end result is less book keeping, and the benefits of "pure" objects without the overhead.

Of course this is just an augmentation of the underlying react system. You still have access to the react state object, and can use it if needed to directly update state that way.