24

I'm new to React.js and struggling to understand few core concepts to decide should we use this library for our application. My main problem is actually handling update in model that fetched from server.

Imagine, that I have a page that should display five different models. I've built it in the way described in this article: http://facebook.github.io/react/blog/2013/11/05/thinking-in-react.html, so I have "root" component where all the 5 models passed and using props they are going down to the components that hold this models. So, now 2 models updated (I get this events from my model code that live outside of react components) and I need to reflect this on UI. What is the best way to do this?

I'm thinking about following options:

  1. Run renderComponent with a new data once again and rely on the DOM diff react techniques. I have a concern with this, since I'll need to do this on any small change of data.
  2. Call setState for the components that holding this models. In this way, data become not prop, but state which is (from what I understand) not a good practice. Plus, I don't see any way to get ref to the child component outside of the root component.
  3. Having multiple renderComponent calls, so this way I will have access to setProps for any of this component. But then I will need to do some templating work (to have all the containers available on the page) and it kills all the react idea.
  4. Having one root component that include all the possible models in application displayed to users and call setProps for changing models. My concern here, that this component will grow pretty big and become "spaghetti" at some point + concerns from point 1.

Thank you in advance and hope I was able explain my problem clearly.

Sergey Shvets
  • 455
  • 1
  • 4
  • 11
  • 1
    Does your model layer use events? if so you can use them to trigger a render. – krs Dec 16 '13 at 16:13
  • I've added a similar question regarding using Backbone models with React and how you should update the views when the model updates http://stackoverflow.com/questions/20371566/handling-backbone-model-collection-changes-in-react-js – Markus-ipse Dec 17 '13 at 05:58
  • 1
    Have you found a solution? – copndz Sep 08 '14 at 15:47

3 Answers3

6

Calling renderComponent again with the same component but different data is equivalent to calling component.setProps(). So either keep all the models as state in the least common denominator, or just call setProps/renderComponent again when it changes.

krs
  • 4,096
  • 19
  • 22
  • Do you have only one root component you create through renderComponent? Or you have a separate components that renders to different containers for each logical part of your application ? Thank you for reply and trying to help me! – Sergey Shvets Dec 16 '13 at 17:48
5

If you pass the data as props down to your child component, you can simply update it at a higher level and it will force a render to all components that uses the same property object. Consider this simple example:

var World = React.createClass({
    render: function() {
        return <strong>{this.props.name}</strong>;
    }
});

var Hello = React.createClass({
    clickHandler: function() {
        this.setProps({ name: 'earth' });
    },
    render: function() {
        return (
            <div>
                Hello <World name={this.props.name} />
                <button onClick={this.clickHandler}>Click me</button>
            </div>
        );
    }
});

Now, when the user clicks the button you change the property on the Hello component, but since you passed the same property (or data) object to the children, they will react to it and update it’s shadow DOM accordingly.

Here is a fiddle of what I mean: http://jsfiddle.net/xkCKR/

If you have an external data object, you can just pass it to the top component. Just remember that this doesn’t mean that there is a two-way binding:

// simple example of a data model
var Data = { name: 'world' };

var World = React.createClass({
    render: function() {
        return <strong>{this.props.data.name}</strong>;
    }
});

var Hello = React.createClass({
    clickHandler: function() {
        this.setProps({
            data: { name: 'earth' }
        });
    },
    render: function() {
        return (
            <div>
                Hello <World data={this.props.data} />
                <button onClick={this.clickHandler}>Click me</button>
            </div>
        );
    }
});

React.renderComponent(<Hello data={Data} />, document.body);

This works because react uses one-way binding of properties. But if say your child component would update it’s properties, it won’t climb up to it’s parent. For that you’ll need the ReactLink add-on or use a pub/sub interface like the one Backbone provides.

holsee
  • 1,974
  • 2
  • 27
  • 43
David Hellsing
  • 106,495
  • 44
  • 176
  • 212
  • 3
    All you are doing here in from inside React, and what I think the op wants is to "refresh" the dom from outside reactjs. – copndz Sep 08 '14 at 15:51
  • yes, and also I do not understand the one-way concept, here is one shoot binding, once done, even if I change the data in the outside model this has no effect in the rendered class: this.props.model.modelproperty is not checked for updates – Daniele Cruciani Dec 30 '14 at 09:20
  • 2
    You're not supposed to change props, you need to reflect any change in data by calling setState, only this will trigger a rerendering of the affected components and subcomponents. – markus Feb 01 '15 at 16:53
  • 1
    @markus this answer is quite old in React-age, but it’s still OK to use `setProps` on the root element to pass new data according to the docs. However manually triggering a re-render using something like `React.render()` would also work. The point is that React will re-calculate the virtual DOM and refresh accordingly either way. Using state for data is not something I would do, because you can easily end up with multiple truths if you encapsulate data inside a component. – David Hellsing Feb 04 '15 at 11:00
  • @David seems I have to read the React documentation again because I seem to have misunderstood some core concepts. – markus Feb 04 '15 at 14:45
  • Still not understanding the interaction with data (e.g. calling the server for updated data). Where does that play into the React.js lifecycle? I assume you don't want to do ajax calls inside of React.js. Where is that handled? – Ryan Apr 16 '15 at 11:23
5

At the moment I know at least three ways to pass new data to a component:

  1. Re-render component. Do not worry about efficiency of this method because React seems to handle this very well. There are nice articles about this: Change And Its Detection In JavaScript Frameworks and Updating with React.render
  2. Use PubSub to allow component to be notified on data change (some helpful examples you can find in the How to communicate between React components post).
  3. Binding with a callback (see three jsfiddles below)

For the third option I was inspired by the answer of StevenH and extended it a little. Please check my implementation at jsfiddle.net/kb3gN/12002/.

var Data = { value: 1 };

var dataChange = function(callback){
    if(callback){
        callback(Data);
        setInterval(function(){
            Data.value++;
            callback(Data);
        }, 1000);
    }
    return Data;
};

var World = React.createClass({
    render: function() {
        return <strong>{this.props.data.value}</strong>;
    }
});

var Hello = React.createClass({
    getInitialState: function() {
        return {
          data: this.props.dataChange()
        };
    },
    componentDidMount: function() {
        this.props.dataChange(this.updateHandler)
    },
    updateHandler: function(data) {
        this.setState({
          data: data
        });
    },
    render: function() {
        return (
            <div>
                Value: <World data={this.state.data} />
            </div>
        );
    }
});

React.renderComponent(<Hello dataChange={dataChange} />, document.body);

Also there is an extended version at jsfiddle.net/kb3gN/12007.

function ListenersService(){
    var listeners = {};
    this.addListener = function(callback){
        var id;
        if(typeof callback === 'function'){
            id = Math.random().toString(36).slice(2);
            listeners[id] = callback;
        }
        return id;
    }
    this.removeListener = function( id){
        if(listeners[id]){
            delete listeners[id];
            return true;
        }
        return false;
    }
    this.notifyListeners = function(data){
        for (var id in listeners) {
          if(listeners.hasOwnProperty(id)){
            listeners[id](data);
          }
        }
    }
}

function DataService(ListenersService){
    var Data = { value: 1 };
    var self = this;

    var listenersService = new ListenersService();
    this.addListener = listenersService.addListener;
    this.removeListener = listenersService.removeListener;
    this.getData = function(){
        return Data;
    }

    setInterval(function(){
        Data.value++;
        listenersService.notifyListeners(Data);
    }, 1000);
}
var dataSevice = new DataService(ListenersService);

var World = React.createClass({
    render: function() {
        return <strong>{this.props.data.value}</strong>;
    }
});

var Hello = React.createClass({
    getInitialState: function() {
        return {
          data: this.props.dataService.getData()
        };
    },
    componentDidMount: function() {
        this.props.dataService.addListener(this.updateHandler)
    },
    updateHandler: function(data) {
        this.setState({
          data: data
        });
    },
    render: function() {
        return (
            <div>
                Value: <World data={this.state.data} />
            </div>
        );
    }
});

React.renderComponent(<Hello dataService={dataSevice} />, document.body);

This implementation is not completely following the idea of isolated components (because Hello component is dependent on the DataService API), but it can be abstracted further and is up to the app developer which app-specific conventions his components will follow. For example see the mix of the first and second examples at jsfiddle.net/kb3gN/12015 (halloDataStatic object and halloDataDynamic callback)

Note: The ListenersService used in the example is following Observer Pattern and the pattern itself has more cons than pros in many scenarios. But beside that, What I wanted to show with these examples is that there is a way of data binding with a callback

<div id="static"></div>
<div id="dynamic"></div>
<script>

function ListenersService(){
    var listeners = {};
    this.addListener = function(callback){
        var id;
        if(typeof callback === 'function'){
            id = Math.random().toString(36).slice(2);
            listeners[id] = callback;
        }
        return id;
    }
    this.removeListener = function( id){
        if(listeners[id]){
            delete listeners[id];
            return true;
        }
        return false;
    }
    this.notifyListeners = function(data){
        for (var id in listeners) {
          if(listeners.hasOwnProperty(id)){
            listeners[id](data);
          }
        }
    }
}

function DataService(ListenersService){
    var Data = { value: 1 };
    var self = this;

    var listenersService = new ListenersService();
    this.addListener = listenersService.addListener;
    this.removeListener = listenersService.removeListener;
    this.getData = function(){
        return Data;
    }

    setInterval(function(){
        Data.value++;
        listenersService.notifyListeners(Data);
    }, 100);
}
var dataSevice = new DataService(ListenersService);
var halloDataDynamic = function(callback){
    var data = dataSevice.getData();
    if(callback){
        dataSevice.addListener(function(data){
            callback(data);
        });
    }
    return data;
};
var halloDataStatic = dataSevice.getData();

var World = React.createClass({
    render: function() {
        return <strong>{this.props.data.value}</strong>;
    }
});

var Hello = React.createClass({
    getInitialState: function() {
        var data;
        if(typeof this.props.halloData === 'function'){
            data = this.props.halloData(this.updateHandler)
        }
        else data = this.props.halloData;
        return {
          data: data
        };
    },
    updateHandler: function(data) {
        this.setState({
          data: data
        });
    },
    render: function() {
        return (
            <div>
                Value {this.props.name}: <World data={this.state.data} />
            </div>
        );
    }
});
</script>

React.renderComponent(<Hello halloData={halloDataStatic} name="static"/>, document.getElementById('static'));
React.renderComponent(<Hello halloData={halloDataDynamic} name="dynamic"/>, document.getElementById('dynamic'));
Community
  • 1
  • 1
Bulki S Maslom
  • 1,307
  • 10
  • 6