Closed scottmas closed 7 years ago
This is not fully related, but you can implement stuff similar to this with transforms.
We do this with gloss (see all the $props). Example usage:
And for custom properties that do logic, we made babel-jsx-if, so you can do if={bool}
:
Nice usage of transforms! I have investigated babel transforms a bit to jimmy rig attribute logic, but the problem is that Babel transforms are the wild wild west of React. Literally anything is possible using them. What we need is something standardized so that everyone can develop on the same page.
I guarantee attribute directives would be highly used by developers in the wild if a spec for them could be created.
As a possible spec, I imagine it would be easiest if attribute directives simply created wrapping React components on the fly (e.g. it wrapped the original component), and had the ability to pass through the original component unmodified.
Maybe something like this:
class RandomHide extends React.AttributeDirective{
constructor(){
super();
this.state = this.getRandomState();
window.setInterval(() => {
this.setState(this.getRandomState());
})
}
getRandomState(){
return {
hide: Math.random() > 0.5
}
}
render(origElm){
return this.state.hide ? null : origElm;
}
}
why not use do
expressions?
With do expressions we still have a pretty undesirable developer experience. Contrast this:
{do {
if(true) { <span>'hello world'</span> }
else { null }
}}
with this:
<span *jsxIf={true}>Hello world</span>
The second approach is way more readable. Do expressions can be nice for complex conditional logic, but still don't get us where we want to be.
I'm not making an argument that alternatives to attribute directives don't exist, because there are lots of them: babel transforms, do expressions, plain ol' React components, etc. But each of these options are either verbose (e.g. plain ol' React components and do expressions) or wild, wild west (e.g. babel transforms)
You don't need a else
in the block. I think for tiny expressions, ternaries work fine, or even bool && result
work.
Anyway, there have been already a lot of discussions in #35, and it is still open. This issue is a duplicate of that one. Please discuss there instead.
My point is far more general than #35, although I am very interested in the conclusion to that discussion. Of course we can still use bool && <div>hello world</div>
, but the larger issue I'm raising is why React has zero support for custom attribute directives, which are a cornerstone of Angular and many frontend frameworks.
Are we really so confident that: (1) attribute directives are not useful, and that (2) it would be too breaking to implement them in React/JSX? Until we have a discussion, I don't think we know the answer to either of these questions.
Attribute directives are dang handy, and NOT just for conditional expression. See for example the proposed syntax for an Angular like Flexbox attribute implementation:
<div *fxFlex={33} *fxFlex.gt-sm={67}>Hello world</div>
With all due respect, this issue should be re-opened. @satya164
If attribute logic were to be integrated into React, it could make the whole question of conditional JSX expression solveable in user land instead of in the JSX core specification. And this is just one example of the type of problems that attribute logic lends itself to solving.
We can leave this one open.
We've thought it's more compositional and easier to understand if you break that into separate components (ex: a flexbox component) but perhaps there's value here worth discussing.
Alternative syntax:
const fxFlex = (el, value) => React.cloneElement(el, {
style: el.props.style ? {...el.props.style, flex: value} : {flex: value},
});
<div [fxFlex]={33} />
There's just SOOOO many cool things you can do in so much more conveniently once attribute directives are opened up as an option.
As a straw man proposal, consider if we allowed there to be a single __attr
prop holding an object containing the attribute component and its argument, like so:
React.createElement('div', {__attr: {type: SomeClass, argument: 123}}, 'Some text');
To make this easy to use, of course there would be a JSX transform
<div *SomeClass={123}>Some text</div>
When an attribute directive is invoked, the element will automatically be wrapped inside SomeClass. This is trivially easy to accomplish by monkey patching React.createElement like so:
const origCreateElement = React.createElement;
React.createElement = newCreateElement;
function newCreateElement(...args){
if(!(args[1] || {}).__attr) return origCreateElement.call(this, ...args);
const { type, argument } = args[1].__attr;
args[1] = Object.assign({}, args[1]);
delete args[1].__attr;
return createElementWrapper.call(this, type, {argument}, origCreateElement.call(this, ...args));
}
With this monkey patch in place, an attribute directive implementing an async conditional becomes trivial to implement as you can see in this CodePen here.
Attribute directives gain even more win :1st_place_medal: once multiple attribute directives are supported:
<If condition={true}>
<Bold>
<Flashing>
Hello World
</Flashing>
</Bold>
</If>
vs
<div *If={true} *Bold *Flashing>Hello World</div>
We can argue all day long about the merits of either approach, but most developers would eventually opt for the second approach if given the choice. I'm reasonably confident it would naturally happen for simple wrapping components of the sort I'm describing.
Aaaanyhow, this was a long post. And take everything for the straw man proposal it is. But this is one possible way attribute directives could be implemented. And it doesn't appear like it would break existing code, besides having to blacklist a magic property __attr
or the like.
<div *If={true} *Bold *Flashing>Hello World</div>
vs
{true && <div><Bold><Flashing>Hello World</Bold></Flashing></div>}
vs (with implicit do)
{if (true) <div><Bold><Flashing>Hello World</Bold></Flashing></div>}
Comparing apples to apples, it doesn't seem like that a significant win considering the cost. The only immediate win I see (if I understand it correctly) is that it makes it faster to nest as you don't need to close off the tags as well.
But wouldn't a syntax like this make more sense and be more JSXy for easy nesting then? (or some variation of it)
{if (true) <div <Bold <Flashing>>>Hello World</div>}
thus
{if (true) <div foo="bar" <Bold <Flashing bar="foo">>>Hello World</div>}
Minor point for any of these: <div>{x.length && <span />}</div>
. Not sure how many times I've done this by mistake 🥇
We can argue all day long about the merits of either approach, but most developers would eventually opt for the second approach if given the choice.
This might seem natural for people who are super used to Angular but personally, at least, I'd be pretty bummed to see concepts like conditional expressions and greater-than comparisons reimplemented in JSX. Part of what I love about it is that it doesn't do that.
IMO, JSX semantics don't need to be (and shouldn't be) coupled to html semantics.
I.e., it's not particularly useful or important that a JSX expression look like:
<div *Column>
<ul *List>
<li *ListItem>
<a *Link href="#">click me</a>
</li>
</ul>
</div>
However, it is useful and important that it looks like:
<Column>
<List>
<ListItem>
<Link to="#">click me</Link>
</ListItem>
</List>
</Column>
This illustration may not be convincing if you're used to thinking of React as a way to render DOM, but consider the case of react-native. Things like <ul>
and <div>
no longer have any semantic meaning when your render target is native, but things like <List>
and <ListItem>
certainly do!
@syranide Those are some solid proposals. However, what I don't love is how they destroy the semantic readability of the html. The problem I have is with components that exist solely to modify behavior, not add structure. Components like a theoretical <Flashing/>
component exist to modify behavior of existing elements, they don't actually add structure. For example
<div> {// <-- structural component}
<Widget> {// <-- structural component}
<Flashing> {// <-- behavioral component!!!}
<span>Hi people</span> {// <-- structural component}
</Flashing>
</Widget>
</div>
Whereas with attribute directives all the elements/components in the JSX would define structure:
<div> {// <-- structural component}
<Widget> {// <-- structural component}
<span *Flashing>Hi people</span> {// <-- structural component}
</Widget>
</div>
When all the components on a page are meant solely to provide structure, things become much easier to parse and reason about. The point isn't just to save some key strokes.
@lettertwo Those are some prime examples of structural components that should not be implemented as proposed attribute directives. Attribute directives by convention should exist only to modify behavior of a structural element. I totally agree that your second code example is the correct choice.
@matthewwithanm We all love JSX! Can you describe more what would make you sad? It may be possible to describe some definition of an attribute directive that wouldn't corrupt JSX in your view. Maybe a directive could simply be an observer listening to changes on the component and intercepting the output of the render method. Or something like that, instead of a full blown component. Or maybe the attribute directive could be a class decorator of sorts.
I'm really interested to hear your thoughts. Is there another solution we could find to make React more friendly and inclusive? 75%+ of the dev world have only ever written HTML and attribute directives would make their transition to React much smoother. As long as it doesn't hurt performance or the simplicity of React/JSX? There's a good portion of the dev world who wants their HTML to look like HTML and if we can, I think React should try to appeal to them as well. I think we need to be careful to not fall victim to group mentality where we define ourselves as a community by our differences with other frameworks like Angular, but try to stay as open and as inclusive as possible.
I think the difference of opinion here might center on two different interpretations of what JSX is for. (I don't want to put words in any of your...keyboards, so correct me if i'm not on target with any of this!)
One idea of JSX is that it is a way to express DOM (or, if you squint, native elements). From this perspective, it makes a lot of sense to strive to make your JSX as close to the 'metal' as possible. (i.e., you might genuinely want to know that what you're rendering is a <div *Thing *Flashing />
rather than a <Flashing><Thing /></Flashing>
).
Another idea of JSX is that it is a way to declare a function composition. From this perspective, <Flashing><Thing /></Flashing>
translates to Flashing(Thing())
, which is a perfectly decent way to compose a flashing behavior into a thing.
I believe that the second idea of JSX is what makes concepts like attribute directives or logic/flow control components seem less compelling.
Admittedly, the first interpretation of JSX is much closer to what many React devs are actually doing with React (rendering DOM), but i think there is a lot of elegance and power in React (and JSX) that is lost in the details of trying to mold JSX into an HTML-like template language.
Though the first interpretation does jive with the most common product of working with React (though less so for non-DOM render targets), I think the second interpretation is more in line with what many React devs are thinking when working with React, which ultimately matters more to the developer experience, both short- and long-term, IMO.
Those are some great points Lettertwo. I'm totally on board with keeping React functional in nature. Function composition is a beautiful thing and React should stay functional.
Because of this, I totally agree that flow control should never be done via attribute directives
//Should never be possible!!!
<div *reactFor={items as item}>{item.name}</div>
And if attribute directives are simply wrapping components, this is already impossible. Attribute directives are one to one translatable to components. Full stop.
At that point, attribute directives would simply become a syntactic sugar for wrapping components.
Flashing(Thing())
and <Flashing><Thing /></Flashing>
and <Thing *Flashing/>
would all mean the exact same thing. See my Code Pen for a concrete example of this in practice.
With a syntactic sugar like this, it maybe gets a bit harder to picture visually the nested function wrapping, but we lose no functionality and become more developer friendly. Most importantly, we keep the functional nature of JSX/React.
Also, with an appropriate JSX transform, we could keep the exact same rendered semantics:
<div *Attr>Hi</div>
gets transformed by JSX to
React.createElement(Attr, null, React.createElement('div', null, 'Hi'))
@scottmas why not write one now to test it out? Would be a good way to test the proposal. You could use $props
style to avoid needing to modify language.
@matthewwithanm We all love JSX! Can you describe more what would make you sad? It may be possible to describe some definition of an attribute directive that wouldn't corrupt JSX in your view.
The thing I love about it is that it doesn't introduce a bunch of new concepts. Describing JSX now is basically like "It's a way of representing something that's going to be rendered. The attributes are passed to the component as props." That's it. It's basically as complicated of a jump as the one from arrays to array literals. What if you want to create one conditionally? Well, you do the same thing you'd do if you wanted to create an array conditionally. "It's Just JavaScript." 😊
Maybe a directive could simply be an observer listening to changes on the component and intercepting the output of the render method. Or something like that, instead of a full blown component. Or maybe the attribute directive could be a class decorator of sorts.
That's even more concepts…
Attribute directives are one to one translatable to components. Full stop.
So you explain to somebody that <Thing *Flashing />
is another way of writing <Flashing><Thing /></Flashing>
. But then they want to pass props to these attributes so we need another syntax for that. Maybe it's something like in your <Thing *Flashing *If={true} />
example. But wait, what prop is that condition? Is there some kind of default prop syntax? Also, oops, did I just write something that means <If={true}><Flashing><Thing /></Flashing></If>
or <Flashing><If={true}><Thing /></If></Flashing>
? That seems like an easy mistake, maybe we need a special way of prioritizing regardless of order? You just end up layering syntax on top of syntax that's separate from JavaScript, and accomplishes something that can already be done but in a less explicit, less clear way.
I'm really interested to hear your thoughts. Is there another solution we could find to make React more friendly and inclusive? 75%+ of the dev world have only ever written HTML and attribute directives would make their transition to React much smoother.
Sorry, but I don't buy the premise. 😞 What makes you think that there's a majority of people writing attribute directives? In my opinion, making React more friendly and inclusive means introducing fewer new concepts—not more.
As long as it doesn't hurt performance or the simplicity of React/JSX?
It does hurt the conceptual simplicity though. You need to know more stuff that isn't "just JS."
There's a good portion of the dev world who wants their HTML to look like HTML and if we can, I think React should try to appeal to them as well.
I'm confused about this one. Attribute directives don't look like HTML.
I think we need to be careful to not fall victim to group mentality where we define ourselves as a community by our differences with other frameworks like Angular, but try to stay as open and as inclusive as possible.
I also don't get how inclusivity is an argument for any particular feature from another framework. Would adding scopes or dependency injection make React more inclusive? I know to somebody who's knee-deep in Angular, it might feel that way but, again, I think the way to real inclusivity is introducing fewer concepts you gotta learn and making sure that each one "pays its way." This one imo doesn't.
@natwe That's a great idea, although I've never written one before. Time to learn more about babel's guts...
Good points @matthewwithanm. We're all here just trying to talk about how we might improve a framework we all love. It fills me with great sadness every time I am forced to work in Angular 2 and believe me you, I am not an Angular fan boy. In fact this whole issue came about from me trying to convince a developer friend that he should use React, but then there were several "killer" features in Angular that he decided he couldn't do without and that aren't concisely implementable in React w/o wrapping attribute directives. The point is, Angular has some features I admire, among them attribute directives. I was only trying to make the point that it is not a valid argument to say "Angular does it, so therefore it's bad". I don't want to fill anyone with sadness either.
The conceptual simplicity of React is something I think we all love and want to keep. Let's keep it "just javascript"™️ - I'm with you 100%. I only offered the idea of attribute directives being decorators or observers as other potential options if the attribute directive
=== wrapper component
line of reasoning doesn't work out.
As for 75%+ of the dev world only ever having written HTML, I still stand by that. Have those 75%+ also used attribute directives? Definitely not. But have those devs ever added a class to an element to hide it? Almost universally, yes. If so, they already understand how this works:
<div *Hide={someVar}>Hi</div>
Whereas they would look at the following in incomprehension:
{someVar && <div>Hi</div>}
I personally prefer the first, but there's no objective reason one of these is better than the other. Lots of React devs, such as yourself, likely prefer the second. However, it is objectively true that many, many more developers in the world will be able to more rapidly understand the first. Attribute directives are a much simpler conceptual jump to make.
I haven't talked about passing props to attribute directives nor multiple attribute directives, but would a JSX transform of the following really be so incomprehensible and complicated?
<div *Attr1={someVar} *Attr2>Hi</div>
to
<Attr2>
<Attr1 default={someVar}>
<div>Hi</div>
</Attr1>
</Attr2>
to
React.createElement(Attr2, null,
React.createElement(Attr1, {default: someVar},
React.createElement('div', null, 'Hi')))
Where attribute directives are applied from left to right and an attribute prop is passed as default
. There could be weird corner cases which these simple rules don't define exactly but I doubt there would be very many.
Supporting something like the transformation described above would produce several effects: (1) barely increase the complexity of JSX - it would still be "just javascript"™️, (2) Make development a lot more pleasurable for many devs (myself included), and (3) Make React much more approachable to a sizable portion of humanity.
Hi @scottmas – I don't want to get involved with the discussion here but please don't state claims like:
However, it is objectively true that many, many more developers in the world will be able to more rapidly understand the first.
as fact when this neither backed up by evidence nor something that everyone in this discussion would likely agree on. Looking forward to continuing to watch this discussion as long as it doesn't get heated.
I haven't talked about passing props to attribute directives yet, but would a JSX transform of the following really be so incomprehensible and complicated?
It does look confusing to me. <div label="title" />
and <div *div={title} />
seem completely different from each other. If you ask me, devs who have only written HTML will find this more alien than just writing nested tags like they do in HTML.
I'm not sure I understand you fully @satya164. I'm not saying there's no conceptual leap at all, just that there's less of a conceptual leap from plain html <div class='hide'/>
to <div *Hide={true}/>
then there is to {true && <div/>}
. At least to me, this seems relatively self-evident, although I haven't conducted a formal poll.
And I am sorry @spicyj, I didn't mean to make divisive statements. I genuinely did think everyone would agree with that statement. Not that it's a better way, but that it's a less of conceptual leap for devs familiar with html.
I'm talking about this,
<div *Attr1={someVar} *Attr2>Hi</div>
being transpiled to
React.createElement(Attr2, null,
React.createElement(Attr1, {default: someVar},
React.createElement('div', null, 'Hi')))
or basically,
<Attr2>
<Attr1 default={someVar}>
<div>Hi</div>
</Attr1>
</Attr2>
It looks nothing like HTML to me.
Anyway, regarding <div *if={true}>Hello</div>
, what is it actually transpiled to?
But have they ever added a class to an element to hide it? If so, they already understand how this works:
<div *Hide={someVar}>Hi</div>
Whereas they would look at the following in incomprehension:
{someVar && <div>Hi</div>}
I'm not sure that someone who only knows HTML would understand how the first form works because it would work exactly how this works:
<Hide><div /></Hide>
Which is analogous to function composition, an entirely different operation from function application via props or attributes.
So while it may look syntactically similar to an html element with attributes, it is semantically very different.
I think that a React dev coming to understand the second form is exactly the point of emphasizing that JSX is 'just javascript'. If JSX adds it's own programming meta language on top of javascript, then it becomes much tougher to justify JSX as 'just javascript', because it will have become a subset of JS (i.e., a template language).
@lettertwo I think you are definitely right that they wouldn't understand how the first form works. But I think in most cases devs would understand what it would do. And that's a fair point that it could maybe lead them astray in their conceptual model of React. I guess I'm optimistic though that this could be gotten around with proper documentation, etc. Any in many cases, devs don't know or care how framework internals work. They just want to use some predefined tools and build their applications without doing anything custom or fancy.
And you are right that JSX being "just javascript" does get muddied once <Component ...
is no longer always equivalent to React.createElement(Component, ...
. That muddying is a sacrifice I'd be willing to make for greater expressiveness, but it definitely would increase the learning curve of JSX. But I do think it would only increase the learning curve, not the actual complexity of JSX. It would still be relatively transparently transpiled to raw javascript React.createElement
calls.
@satya <div *if={true}>Hello</div>
would be transpiled to
React.createElement(If, {default: true},
React.createElement('div', null, 'Hi'))
Of what then, if not actual complexity, is learning curve a function?
On Tue, Apr 25, 2017, 6:30 PM scottmas notifications@github.com wrote:
@lettertwo https://github.com/lettertwo I think you are definitely right that they wouldn't understand how the first form works. But I think in most cases devs would understand what it would do. And that's a fair point that it could maybe lead them astray in their conceptual model of React. And you are right that JSX being just javascript does get muddied once <Component ... as no longer always equivalent to React.createElement(Component, .... That muddying is a sacrifice I'd be willing to make for greater expressiveness, but it definitely would increase the learning curve of JSX. But I do think it would only increase the learning curve, not the actual complexity of JSX.
@satya https://github.com/satya <div *if={true}>Hello
React.createElement(If, {default: true}, React.createElement('div', null, 'Hi'))
— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/reactjs/react-future/issues/52#issuecomment-297183977, or mute the thread https://github.com/notifications/unsubscribe-auth/AA4oQ2gAum8I93V94qUBmTviF6df9wJfks5rznP2gaJpZM4NGv7H .
Perhaps I could have better phrased it "run time programmatic complexity of React". What I mean is that the running React code wouldn't be forced to do anything it doesn't do already. It would still just be dealing with components.
As an analogy, take another "sugar" construct, like class and method decorators. There's basically nothing that you can do with decorators that you can't do with Object.define
, but decorators are arguably easier to write, reason about, and consume. Are decorators destroying the simplicity of Javascript? Maybe a little. But they're also darn convenient. I'm trying to make the same argument for attribute directives.
<div *flashing *highlight={someVar}>Hi</div>
is just easier to write, read, and think about about then its compiled equivalent
<Highlight default={someVar}><Flashing></div>Hi</div></Flashing></Highlight>
And I think the benefits of the above approach become more apparent the less steeped you are in React. Talk to any dev on the street who hasn't programmed in React and ask them which of the two above code snippets they would prefer to write.
I recognize the same argument can be made for any syntactical sugar proposal. So really it's a question of costs vs benefits. How many developers would the sugar make happier + make more inclined to use React vs how many devs would it make mad + make less inclined to use React? I'm inclined to believe there's a lot of devs out there (likely not in this conversation) who would like the addition a whole lot, and a decent amount of existing React devs who would like it too despite the added complexity to JSX.
yo dawg, it looks like you've put a component in a component :P But seriously, it does look like this is just trying to hide wrapped components behind new syntax, so I'd ask why you wouldn't just create another component instead?
const BoldFlashingComponent = (props) => (<Bold><Flashing>{props.children}</Flashing></Bold>);
...
<BoldFlashingComponent><span>Hi</span></BoldFlashingComponent>
or
@Bold @Flashing (<span>Hi</span)
I much prefer the explicit nature over using the attributes for the sake of terse embedded code here <span *bold *flashing>Hi</span>
. And I definitely don't think the extra line is problematic (of course I've never been one for trying to make things terse for the sake of saying more with less characters).
Now I'm not exactly saying the special attributes wouldn't be useful for declaratively describing behaviour modifications on a component, in fact I think it seems like a great idea, although I admit I'm coming up blank on types of behaviour changes that I would feel the need to have them embedded within jsx rather than having the logic separate in code or another explicit component.
I would also hate to see conditional logic used this way. Trying to create a component to do conditional logic, like an if, seems like an extremely heavy handed way of doing things. And it's extremely expensive in comparison to the simple branch that it's trying to duplicate ie. {condition && (<div />) }
. I love jsx specifically because the logic is completely separate from the component html-like syntax. And cause the component syntax doesn't try to hide anything from me. The only thing I would like see related to conditional logic is a better way to do a single if statement so that I can pull the component syntax outside of my logic blocks { ... }
...more like {{ conditional }} <Component />
(but this is probably better for a different issue).
Are there other use cases you can think of that this syntax would enable? ie things that can't be done currently rather than just ease of writing?
Ok I finally came up with some other uses for myself. For example, react uses the attributes ref and key, but moving them to these special attributes could free up the attributes for the actual component. So you could treat this syntax as a way of setting metadata on a component.
<MySecurityComponent key="cipherkey" *key="react-unique-key" />
Then if the jsx implementing library gave you some hooks to do something with the metadata, you could register some middleware that would be able to interpret the metadata however you want.
So in your case you could add in a styling middleware. babel plugin: [jsx-attribute-styler]
<span *style={bold} *style={flashing} >Hi</span>
And *style could be interpreted as something that wraps the span with a Bold and Flashing component in a specific order (order may be difficult if it's just a map though, but diff issue), or it could set some css flags, all depending on what the middleware does. And something like the babel react preset would enable the jsx-attribute-react plugin to read key and ref attributes.
The beauty with this is it provides the ability for the generic hooks, but makes it a user buy-in, which then bypasses all of the arguments 'we all' have for and against different syntax styles for enabling logic and other capabilities. Ya the more I think about this, the more I think this could be awesome and provide the community with lots of power to do what they want without alienating react/jsx devs that like their syntax a specific way.
This entire argument seems to be based on a disagreement on a single issue:
JSX is an extension of JavaScript, so in order to learn JSX you should first understand basic JavaScript and in order to become better at JSX you should become better at JavaScript.
The argument seems to be that because JSX looks kinda like HTML which can be understood without understanding JavaScript, it should also be possible to read and write JSX without understanding even basic JavaScript.
The one issue this argument completely overlooks is that there is no way to have logic in JSX without requiring some knowledge of something other than basic HTML. The proposed additions extend JSX with custom proprietary non-HTML syntax. This is pretty much the route template languages like Angular2's take. Naively this seems like an easy fix because "at least you don't have to learn JavaScript" but it brings with it all kinds of problems:
If we're just talking about extending JSX without making it a proper template language we still run into problems with evaluation semantics: conditions in if
statements and boolean expressions are evaluated using short-circuiting which makes things like (x && x.y && x.y.z)
possible (even when x
is undefined
). By nesting boolean expressions you can avoid instantiating components you're not going to use or for props you won't actually have. This is a language feature of JavaScript itself.
In order to make stuff like *If
work you would have to avoid creating the child component instances until the expression is evaluated. Creating the child component instances ahead of time would make it useless. However changing these evaluation semantics would potentially be even more confusing because it's additional semantics newcomers would have to learn, making JSX even more complex.
Hey Pluma, I've seen your work on ArangoDB before. It's a nice looking database.
I think our disagreement is mostly a matter of degree though. With the current proposal, *If
would work in exactly the same way as <If condition={false}>Hi<//If>
would work. Which would not work with an undefined
variable. The good news is that with this type of nested component implementation, your concerns about further complicating the evaluation semantics would be unfounded. The babel plugin would be relatively simple, and not break any existing React code.
But like I said, it's a matter of degree.
On one end of the spectrum, there's a lot of React purists who loathe and despise JSX. They want pure functional goodness in their UI's, without JSX sugar obfuscating the actual React function calls. In their view it makes things simpler and cleaner. And they are entitled to their opinion. With syntax sugar like JSX, there really is no "right" answer.
Somewhere in the middle, we have JSX as it currently stands. Relatively transparent mapping from <div/>
to React.createElement('div', ...)
. It's brings some of the visual niceties of HTML to Javascript. Developers are forced to learn Javascript, not some DSL. This happy combination of easy on the eyes structure + just needing to know javascript is one of the original reasons I fell in love with React. I don't want to change that. I like it.
A little further on the spectrum is this proposal. We add some attribute goodness to JSX, with a relatively transparent mapping from <div *Attr/>
to React.createElement(Attr, null, React.createElement('div', null, null))
. It makes the mapping from JSX to Javascript a bit more complex, but not by much. For people like me, I feel like it keeps the pure Javascript functional goodness, but just adds a little more JSX'ey goodness on top. In the same way that JSX itself makes raw Javascript a little easier to work with. In my view, some components are better thought of behavior modifiers, rather than structural components, and would be better to write as attributes rather than nesting parent components.
At the extreme end of the spectrum, we have frameworks like Angular. Lots of people love it. I don't. It becomes this hugely complicated DSL with a million lifecycle hooks and triggers, and it becomes very hard to follow what's going to happen next. Suddenly you need to become an Angular developer, not a Javascript developer.
So basically what I'm saying, is that this proposal would add a little more syntax sugar on top but not at all change the fundamental nature of JSX. It would still be just Javascript. There would just be one more option that people like myself would find extremely useful and easy to work with.
Really I just need to stop talking about this and create a babel JSX plugin!
But this thread is exhausting me. I feel like JSX has become such a part of so many React devs identity, that a pretty big proposal like this (even though backwards compatible and requiring nothing more than a babel transform) just hits someplace deep.
So closing this for now. Maybe it'll be worth opening if I or someone else create a babel plugin proof of concept and we can discuss relative merits, etcetera, more concretely.
Long thread, lots of great opinions. I'm coming from AngularJS, and I'm learning to live without directives, but there is one use case I really miss.
Form validation was never easier. I could write a ton of little single use validator functions and drop them on an input to change its behavior. For example:
<input type="text" validate-email="multiple" validate-min-length="5" validate-has-chars="@mydomain.com" />
I know I can find ways to express the same logic without attribute directives, but it ends up being extremely ugly, and I feel like I'm adding semantic structure where I really just want to upgrade logic.
My current solution for this particular case is to create a form validator component and pass it list of validations for the child element. This might fit this particular case with minimal bloat, but it does feel like a workaround for what could be a much simpler directive API.
<Validate validators={{email: 'multiple', minLength: 5, hasChars: '@mydomain.com'}}>
<input type="text" />
</Validate>
@jamesmfriedman or make a custom Input
component <Input type="text" validate-email="multiple" validate-min-length="5" validate-has-chars="@mydomain.com" />
Yeah, that would be a bit cleaner, thanks for the tip.
@jamesmfriedman Yes. This. Form validation is perfect example in my view of something that are perfectly suited to attribute directives.
Using only components, it becomes impractical to compose atomic directives in a practical way. Instead of composing components, devs are forced to use kitchen sink all in one components of the type that you created or which @KyleAMathews created. And if the all-in-one component doesn't have the particular validation you want, they have no recourse other than to submit a PR, use a new library, or try some potentially kludgy work around.
But the compositional approach looks like this:
<ValidateEmail arg="multiple">
<ValidateMinLength arg="5">
<ValidateHasChars="@mydomain.com">
<input type="text" value={this.state.value}/>
</ValidateHasChars>
</ValidateMinLength>
</ValidateEmail>
Which this is monstrous, and I personally would never do it this way. The code becomes too hard to follow.
But with syntactic sugar, it becomes sane once again:
<input type="text" *ValidateEmail="multiple"
*ValidateMinLength="5"
*ValidateHasChars="@mydomain.com" />
The big benefit I'm advocating here is that attribute components allow us to use component composition in a practical way. Which opens up a whole new way of thinking about the components we write.
When components are simple, they can become the infrastructure for more complex components - in the same way that dead simple NPM packages you've never heard of become the building blocks for more complex ones. This building blocks approach simply isn't possible with the monolithic kitchen sink components React devs have traditionally written.
Also, kitchen-sink components begin creating these weird React DSLs of <Input>
's created by four different package authors (case in point: react-input, react-input-component, react-input-field, and react-text-input) instead of letting us stick with the plain DOM primitives and extend their default behavior.
@scottmas IMHO none of those seem sane. The syntax might look nice, but it seems like you're abusing the concept of components. This can and should be accomplished using plain functions. It's flexible, reusable and accurate. Because the same validator can now also be used before submitting to be doubly sure that values are OK, rather than somehow relying on storing validator results in state.
let emailValidator = createValidator([validatorMinLength(...), validatorEmail(...)])
// ...
let validateResult = emailValidator(this.state.email);
return (
<div>
{validateResult ? <ValidateWarning result={validateResult} /> : null}
<input type="text" ... />
</div>
);
That's just POC and much more flexible. You can stick it in your own flexible component if you like to make it easier to reuse. There are lots of ways to do this.
So it could look like this:
<MyInput validator={emailValidator} ... />
And you could take it even further if you're doing forms on a large scale:
let fields = [
{name: 'foo', type: 'blah', validator: blahValidator},
{name: 'bar', type: 'email', validator: emailValidator},
];
<MyForm fields={fields} onValidSubmit={...} ... />
Again, this is just what I've come up with on the spot. There may be tweaks or entirely different ways that are more suitable for various purposes. But the point is that using the existing tools you can accomplish the exact same thing you're trying to invent a new syntax for, but using more sensible runtime features (nesting validator elements does not seem sane). The best solution depends entirely on how much convenience vs flexibility you want.
@scottmas, to @KyleAMathews's point, your example glosses over the fact that you could simply implement a component that renders an input and performs validation from props. So, instead of this:
<input type="text" *ValidateEmail="multiple" *ValidateMinLength="5" *ValidateHasChars="@mydomain.com" />
You could have this:
<EmailInput multiple minLength={5} hasChars="@mydomain.com" />
The cost of directly using the 'primitives' from react-dom to preserve semantics is that you miss out on the power and expressiveness of React's component model almost entirely.
@syranide, your pseudo code looks like perfectly sane and normal React code in the way we have traditionally done things.
But in what way am I "abusing the concept of components"? Your point about needing to store validator results in state is a good one, and could take some thought on how to address. But specifically, why is nesting lots of components inherently a bad thing? Some slight performance concerns are the only thing I can think of.
I'm interested in your perspective also on the points I raised, that the traditional approach forces us to
blahValidator
)<EmailInput/>
instead of the primitives exposed by the environment (<input/>
in the DOM, <TextInput/>
in React Native, etc). @scottmas
But in what way am I "abusing the concept of components"? Your point about needing to store validator results in state is a good one, and could take some thought on how to address. But specifically, why is nesting lots of components inherently a bad thing? Some slight performance concerns are the only thing I can think of.
It's a complex topic and I haven't thought about it enough to have all the right terminology. But I think it's inherently wrong to render a validator, a validator takes a value and gives you a result, it's a simple function. It has nothing to do with React. A good validator can be used for any UI framework. But you should render the results of the validation, and how that looks should be flexible. In my daily work I focus heavily on achieving certain design goals. Perhaps I want to show all input validations at the same time, perhaps only the most important, perhaps I just want to show the error on the active input, perhaps I want to have a summary at the bottom. Your solution seems to only accommodate a few of these, and only in certain ways.
So don't get me wrong, the way you've done is not necessarily wrong if you have a very specific outcome in-mind and you're fine with the tradeoffs. But does not seem like a good foundation for a language feature, it needs to have large usefulness and should avoid luring developers into corners.
You should leverage JS for complicated logic, not use JSX and components as some kind of meta language. I.e. the same reasons if/conditional-rendering is done via JS and not JSX directives. JS is far more expressive and suited for task.
Use monolithic kitchen sink components ( - still monolithic even if they expose a plugin interface - which is basically what you're describing with the custom validators like blahValidator)
I don't really use components made by other people, we design from scratch for our specific purpose. Partly because of the beliefs of my team, but also because I haven't seen anyone do it right. I truly believe useful reusable component libraries are possible, but the few I've seen have gotten it terribly wrong being trapped in the HTML mindset or are very opinionated and useless to us. But I haven't looked very far.
But I don't see how having a <FooBarInut />
with validator support is "monolithic". It can be a simple input underneath with only the essential code for handling a generic validator interface and what should be done when a validation error occurs. It basically becomes your helper component for achieving a certain functionality, if you want to have different behavior somewhere else, you make a different version of it, but use the same validators, etc.
To put it differently, IMHO the right way to think about these things is; you have a bunch of features and you want to put them together side-by-side, they should all be aware of each other. Having an <input />
which magically become validated because it's nested inside a <MagicValidator />
and which post data to a certain source because it's in a <MagicForm>
is really alien to me.
Force us to use DSL abstractions like
instead of the primitives exposed by the environment ( in the DOM, in React Native, etc).
That is fine if you have very specific needs for certain components (i.e. perhaps the email input has some visual aids or whatever), other than that if you end up with that result you've done it wrong.
EDIT: I never use the raw <input />
directly, it's always hidden inside one or more (reusable) components.
Require lots more logic and code. Your example is perfectly sane traditional React code, but it is quite verbose compared to the proposal.
Yes it's a lot of code, but the point is that you then stick this code in a simpler reusable component with a very specific purpose, i.e. validate my field and show the result in a certain way. If you're unhappy with that later, want to do it another way, perhaps you have a field which is very special... then you're now free to take that code and make a different component. Flexibility. But you're now making the trade-offs on a per component basis, not on a project-wide basis.
The way I think about this is that I want to have a large set of features than I can package into simple purpose-made reusable components. I do not want to have a bunch of generic feature components which I chain together which can achieve a predefined set of behaviors but can go no further.
So I'm not afraid of writing a little more code, that's how my team operates. We prefer to achieve specific design goals over writing short code, we do not evolve our design goals from predefined components. Flexibility trumps convenience.
Again, I haven't thought this through massively, but I hope some of it makes sense. If you're trying to achieve certain practical goals (flexibility, reusability, etc) then one can discuss code or more concrete aspects around it. But if writing terse code is the goal irrespective of technical demands, then it's in my opinion pointless as it always devolves into each individuals' thresholds for how many shortsighted shortcuts they're willing to make irrespective of the technical trade-offs.
I just wanted to add one more example to the discussion. I'm currently tasked to put tooltips over different components. The way you do this is as follows:
`
`
As opposed to:
<SomeComponent *toolTip="tooltip"></SomeComponent/>
Admittedly, there's a shorter version of this, and that is to create a function and just do:
toolTip("text tooltip", <SomeComponent></SomeComponent>)
But, it's still pretty pedestrian and don't get me started on when you start composing multiple functions. I don't know, JSX obviously has its great sides (eg. debugging, simplicity), but i do feel that historically it's absolutely a step backwards in expressiveness compared to previous frameworks i worked with (XAML,Angular etc.). JSX is easy, but not simple.
JSX is easy, but not simple.
I am not sure if you intended to quote Rich Hickey, but simple and easy are very loaded adjectives in our field and IMHO you got definitions wrong.
Compared to Angular's attributes JSX is the simpler technology: it has few, well defined semantics that are not surprising and do not require too much to explain. OTOH Angular's attributes require a lot of explaining, and allow you to generate something that is complex with fewer keystrokes, that makes them easy, not simple.
Probably the wrong analogy. If you look at it from the point of toolsets, where you need get something done, the simple tools and concepts that React provides you don't scale that nicely with data intensive apps; They're easy to pick up, but hard to master in a way that doesn't leave you perpetually uncomfortable and writing boilerplate. The way i have to think when using react and the redux to handle complex cases does leave me scratching my head and feeling like i'm dealing with unfit tool for the job a lot (the tooltip is just one example, redux-form library, although the best forms library, suffers a lot from the same type of issues, where boilerplate and boilerplate wrapping is your only way out sometimes). I have a junior and i really can't explain some of the code and data flow to him when it comes to react, it just gets MESSY. Angular directives are more complex, but they adress the need, and address it well in a pretty DRY way. You do need some extra effort upfront but it pays dividends when you start getting it. You simply need that. You need a way to sprinkle behaviour on elements in a sane way, because that's what a huge majority of modern UI libraries I used have in one way or the other. I do believe that when doing that simple/easy judgement, it heavily depends on your use case too. So yeah, maybe Angular is not simple or easy, but there's nothing simple or easy about our job either.
For different reasons than the ones you mention I agree that redux-form
's API is not very good anymore if you consider how much the react community progressed in the past years. That is not React's fault in any way though (and there are better new libraries).
I have wrapped redux-form
in a functional API with great developer ergonomics (using fp-ts and io-ts, two libraries that I can really recommend), since I have so many redux-form
forms in my project, that it doesn't make sense to move away from it.
I am not really sure I'd want to add validation information directly in the JSX. As your application's scope increases, it's generally a good idea to move that stuff away from the view code anyway. As mentioned in previous comments though, if you want to, you can do it in React as well by creating a Field
component that takes validations as props.
Not so much interested in validation use case, as in having the simple ability to add a tool tip to a component without hijacking the component itself, which happens alot because there's a whole class of problems that are taken care of be HOC which conceptually turn the whole thing on it's head, now your damn helper is a container of your component instead of other way around!
There's also a whole set of tools like redux-orm and immutable which try to control the data flow in a controllable way. But then, you made it easy, but it aint simple anymore! Trying to debug those sorts of hairballs gets old fast.
What i'm trying to say, React is like Paint.NET to me, and Angular closer to Photoshop. Sure, i can't do jack in photoshop and it would prolly take a month course to grasp it and start doing stuff, but React needs a crapload of plugins and helpers which get you eventually to the same point, only it sucks. Like you just said, redux-form is not hot anymore and now i feel like a made a wrong call, but guess what, angular forms is still a good decision!
On a constructive note, i'd be interested to have a look at your favourite alternatives to redux-form. Cheers.
Edit: For those of you reading this thread through from the beginning to get some context, below is the original post which uses the example of an
if={condition}
attribute directive. However, upon further discussion this is actually a bad example. Really the primary purpose of an attribute directive would be to "sprinkle" behavior onto existing components without modifying their core functionality. For example, a better instance of this would be something like a<img tooltip="message"/>
, where the tooltip directive simply sprinkles on tooltip functionality onto the image component.You can read down for more discussion, but basically this behavior could be implemented easily enough by making attribute logic simply syntactic sugar for wrapper components (e.g.
<Tooltip message='message'><img/></Tooltip>
. There are some fiddly bits to iron out (particularly with parameter passing and order of invocation) and desirability (some think it's an abuse of the proper purpose of components) but it is is technically possible.Original post follows: