Reusability/Scalability issues with react-flux app - javascript

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.

Related

Store fetched data to avoid fetching it with each reload of the component

I have a React component that is supposed to be used in different views. This component is Select element with pretty long list of options, and I load this list from db via ajax call.
For all views used the same Select with the same list of options.
My problem is that I don't understand how to coerce this Select to load this list only once, and reuse it for other renderings.
It looks somewhat like this now (simplistically, obviously):
var SelectField = React.createClass({
loadListFromServer: function () {...}
getInitialState()
{
return {
options: [],
};
},
componentDidMount() {
this.setState({
options: this.loadListFromServer()
});
},
render: function () {
return (
<div>
<Select options={this.state.options} />
</div>
);
}
})
;
var Index = React.createClass({
render: function () {
return (
<SelectField />
);
}
});
var Content = React.createClass({
render: function () {
return (
<SelectField />
);
}
});
ReactDOM.render(
<Router history={createBrowserHistory()}>
<Route path="/" component={Index}/>
<Route path="path" component={Content}/>
</Router>,
document.getElementById("container")
)
What I tried to do: make both options and loadListFromServer() global and call the loadListFromServer() from window.init. Then Index renders with empty options as it is being filled when everything is already mounted.
So what is general approach to achieve it? Thanks, and I am sorry if my question is stupid - I've just started this topic.
When you say you only want to load the <Select> component once, I assume you mean you only want to load its data once.
You might try a flux architecture that separates components from actions.
The root of the problem in your example seems to be the tight coupling between the <Select> component and the state that it presents (the list of options). Every time the component is used, it must create its state or reuse the state from a different instance of <Select>. But in the latter case we would need somewhere to store the state between different instances of the component.
Have you looked into redux? It decouples the state from components.

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

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);

React JS inheritance

I have question regarding React and especially the way it is supposed to share common functionality. From what I read, the preferred way is using mixings, correct?
I have the following problem: I have a toolbar and buttons. Toolbar is one React class and each Button is a separate class. All buttons share same common functionality:
They all create something on init.
They all execute common action on click.
I extracted this common functionality in single mixin. It has two functions for these two similar things. I'm passing what is specific to the functions in the mixin from each button.
The problem is that I have now another button which does no fit in that picture. On click, it should do something completely different. I can't reuse the second functionality (handling the click) from the mixin.
Of course, I thought to create a separate function handleClick only in this button, which will override the one in the mixin. However, it seems this is not possible - React forbids overriding functions, not part of the lifecycle.
Would you give me some advices what is the preferred way to resolve this situation? Maybe to create two mixins and just to not use the second one for the button which does not fit in the common picture?
Thanks,
Another option rather than mixins is to create a base/abstract button that is used by the various specialty types of buttons (through a compositional pattern). Here's a simple demo.
What I've done is create an AbstractButton that has basic button clicking behavior. You could put things that are common to all of your buttons there for example.
With that, I've created a second class called RealButton1. It uses AbstractButton and handles the a click event (onClick). Then, I've created a second specialization of a button called SecondRealButton. Both can behave different and still be reused as necessary.
While mixins work today, there isn't an obvious path forward for them yet given the recent announcements of starting to support EcmaScript 6 syntax. Also, from that same blog post, you'll note how they specifically mention that there isn't any mixin support for ES6 classes.
In this case, I wouldn't recommend using a mixin as you (presumably) own the UI components and aren't trying to provide an extension model for another pluggable system (like might be necessary for example with a router or store/flux/dispatcher type system).
var AbstractButton = React.createClass({
propTypes: {
onClick: React.PropTypes.func
},
handleClick: function(e) {
if (this.props.onClick) {
this.props.onClick.call(this, e);
}
},
render: function() {
return <button onClick={this.handleClick}>
{this.props.caption}
</button>;
}
});
var RealButton1 = React.createClass({
propTypes: {
caption: React.PropTypes.string.isRequired
},
render: function() {
return <AbstractButton
onClick={ this.clicked }
caption={ this.props.caption } />;
},
clicked: function(e) {
console.log('clicked ' + this.props.caption);
}
});
var SecondRealButton = React.createClass({
propTypes: {
caption: React.PropTypes.string.isRequired
},
render: function() {
return <AbstractButton onClick={ this.clicked }
caption={ this.props.caption } />;
},
clicked: function(e) {
console.log('second type clicked ' + this.props.caption);
}
});
var SampleApp = React.createClass({
render: function() {
return (<div>
<RealButton1 caption="button 1" />
<RealButton1 caption="button 2" />
<SecondRealButton caption="other type button #1" />
</div>
);
}
});
React.render(<SampleApp />, document.body);
Two options (or both!):
create two mixins
use a name other than handleClick
In mixins you usually want less generic names. handleClick is very vague, and you can't instantly see the implementation, or even know it's coming from a certain mixin. If you give it a more qualified name like handleToolSelect, it'll be clearer and you can just pretend it doesn't exist in your components that don't need it.

React js - Disable render of a component in a mixin

I'm trying to develop a React mixin to check the user access level before rendering the component.
If the user doesn't have the permission to see the component, I would like to disable the rendering of the component.
I've been looking for something build in react to handle this but found nothing, so I did that:
var AuthentLevelMixin = {
componentWillMount: function() {
if(!Auth.check()) {
// Disable component render method
this.render = function () {
return false;
}
}
}
}
It works as expected but I feel like it's the "dirty way".
So my question is: what is the "React way" for doing the same as this snippet ?
For a mixin this is about the best you can do. It's just a simple early return in render.
var AuthentLevelMixin {
isAuthenticated: function(){
return Auth.check();
}
};
var C = React.createClass({
mixins: [AuthentLevelMixin],
render: function(){
if (!this.isAuthenticated()) return <div />;
return (
<div>...</div>
);
}
});
If you decide to go with your initial strategy (I don't recommend it), it just needs to be modified slightly:
// more explicit names are important for dirty code
var PreventRenderUnlessAuthMixin = {
componentWillMount: function() {
this._originalRender = this.render;
this._setRenderMethod();
},
componentWillUpdate: function(){
this._setRenderMethod();
}.
_emptyRender: function () {
return <span />;
},
_setRenderMethod: function(){
this.render = Auth.check() ? this._originalRender : this._emptyRender;
}
}
If you want to handle the authorization inside your mixin without adding logic to your component you are doing it the right way. BUT: Every component implementing this mixin should then be aware of what happens within this mixin. If the result you expect is, that nothing is rendered, then you are perfectly right with what you are doing. So if your way is resulting in simplicity it is the React-Way. And in my Opinion this is the case.
In the componentWillMount lifecycle event you will capture the moment right before rendering - which is a great time to prevent rendering. So I really dont see anything speaking against your code.
EDIT:
aproach of defining: "react way"
Once you have the same input resulting in the same output every time your code becomes predictable. With your code being predictable you achieve simplicity. These are terms used by Pete Hunt to describe the intentions of React. So therefor if you stay predictable and in result achieving simplicity you are doing it the react way.
In case of the above mixin both these rules apply and is therefor the "react way" in the definition I have provided above.
My advice here would be to not use a mixin. The best way to clean up your component is to remove this logic from the component, and simply not render the component based on the result of checking Auth.
The problem with this is that you have a component that is no longer consistent, because it depends on something other than its props. This doesn't really do much other than push the problem upwards, but it does allow you to have one more pure component.
I can see why the mixin is attractive though, so here's a simpler way of doing what you need that doesn't involve dynamically swapping the render method:
var PreventRenderUnlessAuthMixin = {
componentWillMount: function () {
var oldRender = this.render;
this.render = function () {
return Auth.check() ? this.render() : <div />
}.bind(this);
}
}

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