React js - Disable render of a component in a mixin - javascript

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

Related

React - this.state is empty in render when getInitialState is properly defined

I'll try my best to explain the scenario, as making a fiddle of the smallest possible case might be hard. Essentially, I have a SPA using React-Router. I'm currently getting a strange behavior in specifically one version of Firefox (31.4esr).
I have two sidebar icons which trigger a change in routes, navigating to a new page. On occasion when I switch quickly between them, I'm getting an error that this.state.list is undefined(this is a list that I populate a dropdown with).
The issue is, upon debugging, console.log(this.state) is returning an empty object just before the call (that errors) to this.state.list happens in my render method. However, I have list defined in getInitialState (along with a bunch of other state variables) and so this.state definitely shouldn't be empty.
The only thing I could think of that would be causing this is if due to the quick switching there is some confusion with mounting/unmounting of components and my component still thinks it is mounted, so skips the getInitialState and goes ahead and tries to render. Either that or some bug in React-Router.
Any thoughts? Thanks in advance for the help!
Nick
P.S I should reiterate this also only occurs very rarely during quick switching, and normally the componentDidMount -> getInitialState -> render occurs as expected, so it is not simply an error in how my getInitialState is written etc.
Edit: Using React 0.13.3 and React router 0.13.3
Edit 2: Here is the stripped down version of the lifecycle methods, very basic.
getInitialState: function() {
return { list: listStore.getList("myList") || [] }
},
render: function() {
var newList = [];
//this is the line that errors with this.state.list is undefined
this.state.list.forEach(function(listItem) {
...
}
return (
<div>
<OtherComponent newList={newList} />
</div>
)
};
When putting console.log in componentWillMount (just attaches store listeners), getInitialState, and render, I get output like this when the error occurs:
"Setting initial state"
"2,3" //This is this.state.list in componentWillMount
"2,3" //This is this.state.list in initial Render
Object { } //This is this.state the next time it gets called in render :S.
you will have to use mixin like this:
var state = {
getInitialState: function() {
return { list: listStore.getList("myList") || [] }
}
}
var nameRouter = React.createClass({
mixins : [state],
// more content
})
this is because react-router ignore the getInitialState that was definite

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.

Mixins and duplicate methods in React.js

Getting more and more into the awesomeness that is React.js and I've started to use Mixins more.
One thing I noticed, is that both my mixin and my component can have a componentDidMount method — And both functions will be called, so defining it in the component won't override the one in the mixin and vice versa.
Here's an example:
var MyMixin = {
componentDidMount: function() {
// Do something when component is mounted
console.log("Mixin fn ran");
}
};
var Component = React.createClass({
mixins: [MyMixin],
componentDidMount: function() {
// Do something when component is mounted
console.log("Component fn ran");
}
});
Now, the question is wether this is by design or just a coincidence that this works. The lifecycle methods are very useful (To bind and unbind events for instance), so it's not uncommon that both my component and mixins will want to rely on those. The documentation doesn't say anything about this, and I'm wanting to know if I'm setting myself up for a bad time down the road by doing this.
Another question is do I have some kind of control over which method is called first? The one in the mixin or the one in the component.
Yes, it's intentional, and the main factor that makes mixins very powerful in React.
So what happens is:
for the 'component...' functions, they're called in the order of mixin[0], mixins[1], ..., component
propTypes, and the return value of getInitialState and getDefaultProps are merged
other conflicting method names, or conflicts while merging the above results in an error

Change state of React component from old external Javascript?

How can I change the state of a React component from my old legacy jQuery soup
code?
I have a component like this:
var AComponent = React.createClass({
getInitialState: function() {
return { ids: [] }
},
render: function() {
...
},
onButtonClick: function() {
ids.splice(…); // remove the last id
}
});
When something special happens in the old jQuery soup code, I'd like to
push an id to AComponent.state.ids. How can I do that?
One "obvious" solution is an anti-pattern; here it is:
var componentInstance = AComtonent({});
React.renderComponent(componentInstance, document.getElementById(...));
// Somewhere else, in the jQuery soup. Something special happens:
componentIntance.state.ids.push(1234);
componentIntance.setState(componentInstance.state);
This is an antipattern, according to this email from a Facebook
developer,
because he writes that componentInstance might be destroyed by React.
I would make the component stateless. Store the ids array outside of your component and pass it as a prop with functions that will modify the array. See example on JSFiddle: http://jsfiddle.net/ohvco4o2/5/

Reactjs - Get a component from anywhere in the app

I'd like to know if there is a way to get a component by using some type of id, or by type, similar as you would do in DOM manipulation. Something like:
var Avatar = React.createClass({
render: function () {
...
}
});
React.renderComponent(Avatar({id:'avatar'}), ...);
...
...
var avatar = React.getComponentById('avatar');
avatar.setProps({url = 'http://...'});
// or
var avatars = React.getComponentByType('Avatar');
if (avatars.length) {
avatars[0].setProps({url = 'http://...'});
}
I don't want to keep references of components instances...
setProps is something that you should use sparingly. In fact storing references to "rendered" components in general might indicate that you can structure your code differently. We also limit your uses of setProps to top level components.
var avatar = React.renderComponent(<Avatar .../>, node);
avatar.setProps({ foo: '1' });
is equivalent to this, which fits in a bit better with the declarative model:
React.renderComponent(<Avatar .../>, node);
React.renderComponent(<Avatar ... foo="1" />, node);
You could wrap that render up inside a function call so you could call it at will.
Sorry, there's no (publicly exposed) global registry of mounted React components. If you need to send messages to a component after mounting it, the best way is to save a reference to it. Iterating through the list of all Avatar components seems like the wrong solution to me anyway because it wrecks the composability aspect of components, where each parent component can specify its child's props and trust that outside forces won't change them -- changing this makes your page harder to reason about.
If you provide a jsfiddle of what you're trying to do, perhaps I can be of more help.

Categories

Resources