0

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?

Petr
  • 1,853
  • 2
  • 25
  • 49
  • I'm not sure I understand but can you not use React's setState in the done callback to change the state of the component? – Marek Suscak Sep 17 '16 at 22:12
  • I tried to do it - like now I have "this.props.websites" that I got from PropTypes websites: React.PropTypes.node. I tried to do this.state = {websites: ...} but didn't figure out what should be instead the three dots. – Petr Sep 17 '16 at 22:18
  • Oh gotcha will post an answer in a bit. – Marek Suscak Sep 17 '16 at 22:35
  • I actually managed it by passing an argument in constructor but now I am thinking how to add website and update the this.state.websites – Petr Sep 17 '16 at 22:48
  • Can you share the source code so that I'm on the same page? – Marek Suscak Sep 17 '16 at 22:51
  • Sure :) sorry it's quite confusing the question now. This is the source code: https://github.com/Dudisek/react-rails/tree/master/app/assets/javascripts/components there is a dashboard and two components with ajax requests - one add_website and second delete_website. It's going to the server but I'd like to update also the this.state.websites (receiving new data from the server in json) – Petr Sep 17 '16 at 22:57
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/123612/discussion-between-marek-suscak-and-dudis). – Marek Suscak Sep 17 '16 at 23:02

1 Answers1

2

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.

Marek Suscak
  • 1,086
  • 10
  • 25