How to trigger re-render on model change in ReactJS? - javascript

I am creating a react component using
React.render(<ReactComponent data="myData">, document.body);
Once the data model changes, I call render again using
React.render(<ReactComponent data="myData">, document.body);
Is this the right/recommended way to update my HTML?
Will this utilize the advantages of the React virtual DOM (i.e. rendering only the elements that have actually changed).
Also, should I be using state or properties when passing in myData?

You should be rendering only one main App component which does AJAX requests etc and uses the data model inside its render function to update sub components.
When creating React components you should always keep the use of state minimal and move it up to the top level component, Instead you should use props to render child components.
This article helped me a lot when i was first getting started with React: https://github.com/uberVU/react-guide/blob/master/props-vs-state.md
so something like:
var App = React.createClass({
render: function(){
return (
<div>
<input type="button" onClick={this.handleClick}/>
<Dropdown items={this.state.countries}/>
</div>
)
},
getInitialState: function(){
return {countries: {}};
},
componentDidMount: function(){
var self = this;
$.getJSON("countries", function(err, countries){
self.setState({countries: countries});
});
},
handleClick: function(){
// every time the user does something,
// all you need to do is to update the state of the App
// which is passed as props to sub components
}
})
React.render(React.createElement(App, {}), document.body);

Related

REACT - Retrieve state of the parent and pass to child

I am learning React and have got to a point where I am a bit stuck. Here is the Parent component of my add with the attribute im using to populate its state with a JSON object:
var App = React.createClass({
getInitialState: function() {
return {
clothing:{},
order:{}
}
},
loadClothingItems: function(){
this.setState({clothing:clothing});
},
});
In my render function for App I am passing down the apps props for loadClothingItems like so:
<OtherOptions loadClothingItems={this.loadClothingItems} />
Here is the child component (OtherOptions). There is an a-tag which onClick populates the App state. I then need to get the state that has been added to App back down to OtherOptions in order to generate all my object key properties into li items within the ul:
var OtherOptions = React.createClass({
render:function(){
return (
<div className="other-options-inner">
<a onClick={this.props.loadClothingItems}>Load Clothing</a>
<ul>
{console.log(this.props.state)}
</ul>
</div>
)
}
});
When I initially load the app I get a console log of "undefined" which is expected as I have not yet populated the App components state but when I click the a-tag it gives me "undefined again".
I was expecting to the new state of App being passed down to OtherOptions here so I could take the state and render out some li items. Maybe i'm not going about it the right way to communicate with the parent or maybe i'm completely off-track but either way I am stuck and the documentation hasn't helped.
Thanks
If you need to pass all of the state to the child component then you can do so via props <OtherOptions items={this.state} loadClothingItems={this.loadClothingItems} />
and access it in the child component via this.props.items
Also, I'm not sure what you're trying to accomplish in loadClothingItems, but clothing doesn't exist anywhere so trying to set the state of clothing to clothing will be undefined.

ReactJS - updating components when separate class property is changed

In my project I have an html doc with some react code, for instance:
var Test = React.createClass({
getInitialState: function() {
return {storage: this.props.storage};
},
render: function() {
return (
<h2>
{this.state.storage}
</h2>
);
}
});
In another class, AppMan, I have a property called storageLeft. If I render the Test component like:
<Test storage={AppMan.storageLeft}/>
What is the correct way to go about updating the Test component whenever storageLeft is changed inside the AppMan class? I'm not sure if passing it as a property of the component is the right way to go about it. Initially, all I can think of is doing a setInterval and constantly doing this.setState({storage: AppMan.storageLeft}); or something along those lines. Any better ideas?
Setting state via props is an anti-pattern. You should save storageLeft in the AppMan component's state and then pass that state to the Test component as a prop. A component automatically re-renders itself and its children (if needed) when you change its state.
The docs state that:
To implement interactions, we introduce mutable state to the
component. this.state is private to the component and can be changed
by calling this.setState(). When the state updates, the component
re-renders itself.
This way your components stay in sync when you pass state as a prop.
AppMan.js
<Test storage={this.state.storageLeft} />
Test.js
var Test = React.createClass({
render: function() {
return (
<h2>
{this.props.storage}
</h2>
);
}
});

Reusability/Scalability issues with react-flux app

The question:
Is there any way to have a standard flux workflow - using Actions and Stores inside of a component and still be able to use this component for multiple different purposes, or if not is there any way to have complex nested structure in flux-react app without propagating every change trough a huge callback pipe-line?
The example (If the question is not clear enough):
Lets say I have a couple of super simple custom components like ToggleButton, Slider, DatePicker and more. They need to be reusable, so i can't use any actions inside of them, instead i've defined callback functions. For example onChange on the DatePicker fires like this:
this.props.onChange(data);
I have a custom component lets call it InfoBox that contains a couple of the simple components described above. This component listens for changes for every of its children like this:
<DatePicker ref='startDate' onChange={this.startDate_changeHandler} />
The InfoBox is used for different purposes so i guess it can not be binded to a specific store as well.
I also have a custom Grid component that render many instances of the InfoBox. This grid is used to show different data on different pages and each page can have multiple grids - so i think i can not bind it with Actions and Stores.
Now here is where it all gets crazy, bear with me - I have couple of pages - Clients, Products, Articles, etc.. each of them have at least one Grid and every grid have some filters (like search).
The pages definitely can use actions and store but there are big similarities between the pages and I don't want to have to duplicate that much code (not only methods, but markup as well).
As you may see it's quite complex structure and it seems to me that is not right to implement pipe-line of callback methods for each change in the nested components going like DataPicker > InfoBox > Grid > Page > Something else.
You're absolutely right in that changing the date in a DatePicker component should not trigger a Flux action. Flux actions are for changing application state, and almost never view state where view state means "input box X contains the value Z", or "the list Y is collapsed".
It's great that you're creating reusable components like Grid etc, it'll help you make the application more maintainable.
The way to handle your problem is to pass in components from the top level down to the bottom. This can either be done with child components or with simple props.
Say you have a page, which shows two Grids, one grid of - let's say - meeting appointments and one grid with todo notes. Now the page itself is too high up in the hierarchy to know when to trigger actions, and your Grid and InfoBox are too general to know which actions to trigger. You can use callbacks like you said, but that can be a bit too limited.
So you have a page, and you have an array of appointments and an array of todo items. To render that and wire it up, you might have something like this:
var TodoActions = {
markAsComplete: function (todo) {
alert('Completed: ' + todo.text);
}
};
var InfoBox = React.createClass({
render: function() {
return (
<div className="infobox">
{React.createElement(this.props.component, this.props)}
</div>
);
}
});
var Grid = React.createClass({
render: function() {
var that = this;
return (
<div className="grid">
{this.props.items.map(function (item) {
return <InfoBox component={that.props.component} item={item} />;
})}
</div>
);
}
});
var Todo = React.createClass({
render: function() {
var that = this;
return (
<div>
Todo: {this.props.item.text}
<button onClick={function () { TodoActions.markAsComplete(that.props.item); }}>Mark as complete</button>
</div>
);
}
});
var MyPage = React.createClass({
getInitialState: function () {
return {
todos: [{text: 'A todo'}]
};
},
render: function() {
return (
<Grid items={this.state.todos} component={Todo} />
);
}
});
React.render(<MyPage />, document.getElementById('app'));
As you see, both Grid and InfoBox knows very little, except that some data is passed to them, and that they should render a component at the bottom which knows how to trigger an action. InfoBox also passes on all its props to Todo, which gives Todo the todo object passed to InfoBox.
So this is one way to deal with these things, but it still means that you're propagating props down from component to component. In some cases where you have deep nesting, propagating that becomes tedious and it's easy to forget to add it which breaks the components further down. For those cases, I'd recommend that you look into contexts in React, which are pretty awesome. Here's a good introduction to contexts: https://www.tildedave.com/2014/11/15/introduction-to-contexts-in-react-js.html
EDIT
Update with answer to your comment. In order to generalize Todo in the example so that it doesn't know which action to call explicitly, you can wrap it in a new component that knows.
Something like this:
var Todo = React.createClass({
render: function() {
var that = this;
return (
<div>
Todo: {this.props.item.text}
<button onClick={function () { this.props.onCompleted(that.props.item); }}>Mark as complete</button>
</div>
);
}
});
var AppointmentTodo = React.createClass({
render: function() {
return <Todo {...this.props} onCompleted={function (todo) { TodoActions.markAsComplete(todo); }} />;
}
});
var MyPage = React.createClass({
getInitialState: function () {
return {
todos: [{text: 'A todo'}]
};
},
render: function() {
return (
<Grid items={this.state.todos} component={AppointmentTodo} />
);
}
});
So instead of having MyPage pass Todo to Grid, it now passes AppointmentTodo which only acts as a wrapper component that knows about a specific action, freeing Todo to only care about rendering it. This is a very common pattern in React, where you have components that just delegate the rendering to another component, and passes in props to it.

Updating the view outside of React

If you update the view outside of the React render method does the virtual DOM update? I'm loading a few base templates async and then just updating components with react, I'm just worried if I change out a template am I working against the performance increase of the virtual DOM diff.
It's fine to update the DOM outside of Reacts control, and it's a common way to integrate plugins that doesn't use React into a React component. But you really shouldn't use both React and some other template system to render the same DOM node.
What you should do is just return an empty <div> in your render method, and get a hold of that DOM node in componentDidMount to render something into that node with your other template system.
Something like:
var MyComponent = React.createClass({
componentDidMount() {
var node = React.findDOMNode(this);
otherTemplateSystem.render(node, {some: data});
},
render() {
return <div></div>;
}
});

ReactJS: Move component state

I am trying to implement a dialog-heavy application. Often, a component needs to launch a dialog in a fire-and-forget fashion, so I have a function create_dialog() that takes a React.DOM element and appends it to the dialogs array in the state of the App component (the top-level component) which in its render() function renders all dialogs.
This is easy to use and works fine, but now I want to be able to minimize dialogs and show a little preview in a task bar. The way I tried to do this is to add a new array previews to the Apps state and when minimizing a dialog, I would remove it from dialogs and append it to previews which is rendered into a different element on the page.
The problem is that React.DOM.* elements are just lightweight references that don't store state, so when minimizing dialogs all state is lost because the dialog components have to be remounted (and as far as I know there is no way to really move a DOM element in ReactJS).
Is there an easy way to move/copy the state of a component without making changes to it?
Or is there a different approach?
var Application = React.createClass(
{
getInitialState: function()
{
return {dialogs: [], previews: []};
},
render: function()
{
return (
<div>
<div id="previews">{this.state.previews}</div>
{this.state.dialogs}
</div>
);
},
...
});
var g_app = React.renderComponent(<Application />, ...);
function create_dialog(header, content)
{
var new_dialog_state = g_app.state.dialogs.slice();
new_dialog_state.push(<Dialog header={header}>{content}</Dialog>);
g_app.setState({dialogs: new_dialog_state});
}
var Item = React.createClass(
{
onButtonClick: function()
{
create_dialog('Item info', <ItemInfo data={this.state.item_info} />);
},
...
});
As mentioned in the comments:
Okay, I solved the problem by storing the state outside components. Components now only user their props which are set by a separate Store object.
- DaviD.

Categories

Resources