Search code examples
ruby-on-railsajaxreactjsreact-rails

How to add or delete objects from React - Rails?


I have a Rails app with react-rails gem.

This is my controller:

class WebsitesController < ApplicationController
  def index
    @websites = Website.all
  end
end

This is my view:

<%= react_component('WebsiteList', websites: @websites) %>

This is my React component wit:

class WebsiteList extends React.Component {

  render () {
    return (
      <div className="container">
        <AddWebsite/>
        <WebsiteItem websites={this.props.websites}/>
      </div>
    );
  }
}

WebsiteList.propTypes = {
  websites: React.PropTypes.node
};

In WebsiteItem it's just mapping the array and showing each object.

AddWebsite Component with ajax to get data on the server:

class AddWebsite extends React.Component {
  constructor() {
    super();
    this.state = {
      formValue: "http://www."
    }
  }

  handleChange(e) {
    this.setState({formValue: e.target.value});
  }

  submitForm() {
    $.ajax({
      method: "POST",
      url: "/websites",
      data: { website: {name: "New Web", url: this.state.formValue} }
    })
      .done(function() {
        console.log("done")
      });
  }

  render() {
    return (
      <form>
        <input value={this.state.formValue} onChange={this.handleChange.bind(this)} name="url"/>
        <button type="button" onClick={this.submitForm.bind(this)} className="btn">Add Web</button>
      </form>
    );
  }
}

When someone adds (or delete) a link and ajax is success, I'd like to update the React component as well. Instead of this.props.websites & propTypes - how I could do it for state? Do I need to get the it from ajax or can I use the <%=react_component ... %> in erb?

What is the best way to solve it and create basically a single page app?


Solution

  • Ok, so what you really want is to have a callback in the AddWebsite component passed from the Dashboard where you hold the app state. Please note that the this.state is component bound which means you can not access state of another component. You can pass the state of one component as a property to another though and that's what we're going to use.

    // dashboard.es6.jsx
    class Dashboard extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          websites: this.props.websites
        }
    
        // Don't forget to bind to self
        this.handleWebsiteAdd = this.handleWebsiteAdd.bind(this)
      }
    
      handleWebsiteAdd(newWebsite) {
        const websites = this.state.websites.concat([newWebsite]);
    
        this.setState({websites});       
      }
    
      render() {
        return (
          <div className="container">
            <AddWebsite onAdd={this.handleWebsiteAdd}/>
            <WebsiteList websites={this.state.websites}/>
          </div>
        );
      }
    }
    
    // add_website.es6.jsx
    class AddWebsite extends React.Component {
      constructor() {
        super();
        this.state = {
          formValue: "http://www."
        }
      }
    
      handleChange(e) {
        this.setState({formValue: e.target.value});
      }
    
      submitForm() {
        $.ajax({
          method: "POST",
          url: "/websites",
          data: { website: {name: "New Web", url: this.state.formValue} }
        })
          .done(function(data) {
            // NOTE: Make sure that your API endpoint returns new website object
            // Also if your response is structured differently you may need to extract it 
            // and instead of passing data directly, pass one of its properties.
            this.props.onAdd(data)
            Materialize.toast('We added your website', 4000)
          }.bind(this))
          .fail(function() {
            Materialize.toast('Not a responsive URL', 4000)
          });
      }
    
      render() {
        return (
          <div className="card">
            <div className="input-field" id="url-form">
              <h5>Add new website</h5>
              <form>
                <input id="url-input" value={this.state.formValue} onChange={this.handleChange.bind(this)} type="text" name="url"/>
                <button type="button" onClick={this.submitForm.bind(this)} className="btn">Add Web</button>
              </form>
            </div>
          </div>
        );
      }
    }
    
    AddWebsite.propTypes = {
      onAdd: React.PropTypes.func.isRequired
    };
    

    Please note this is just one of the many possible solutions.