alexgilleran / jsx-control-statements Goto Github PK
View Code? Open in Web Editor NEWNeater If and For for React JSX
Home Page: https://www.npmjs.com/package/babel-plugin-jsx-control-statements
License: MIT License
Neater If and For for React JSX
Home Page: https://www.npmjs.com/package/babel-plugin-jsx-control-statements
License: MIT License
It made sense to me at the very start when I was just writing this for me, but at this point I think it's outlived its welcome. It's bad JSX/XML semantics, it breaks auto-formatting and we have a much nicer construct for this kind of thing in Choose/Otherwise now.
We obviously should keep If
because it's useful for single-branch conditionals - I'm thinking maybe just drop Else
from the documentation as we redo it, making extra sure to point out that Choose/When/Otherwise is equivalent to If/Else with cleaner semantics.
I was expecting For to be able to iterate over objects and their keys/values, but it doesn't look like that's the case. Could you add support for this?
How to use it in Typescript,please show me some examples
Can you note compatibility with RN in the Repo write-up
looks like the transformer is rigged to support only one level of If/For statements.
this will fail:
<If condition={true}>
<div>
<If condition={1}>
<p>bork</p>
</If>
</div>
</If>
Maybe it's possible to create If, For, etc, components in React, without needing to extend JSX? Foe example,
<For items={someArray}>
<div>...</div>
</For>
Then the For
component would see thie child, and clone it for each item passed in to the items prop. It'd work without JSX. I can imagine the code to make that happen.
What are the pros/cons of introducing a compile step versus runtime components?
I appreciate this module and its various connectors. I use it well in an isomorphic app builded with gulp/webpack/babel.
The only problem I have is the missing support of the module by lint tools, here is the output with eslint enhance with the react plugin:
if.jsx
74:9 error 'If' is not defined react/jsx-no-undef
for.jsx
51:11 error 'For' is not defined react/jsx-no-undef
53:19 error "item" is not defined no-undef
54:23 error "item" is not defined no-undef
I can easily add an exception for If
, Else
and For
but I can't skip the warning for the iterator variable (<For each="item" ...
).
Have you faced this problem before ? Is there an eslint plugin (existing or planned) ?
I'm using https://github.com/rtfeldman/seamless-immutable lib for my data and it doesn't work with the native JS map function. It does work with libs like Ramda or lodash. I considered writing a wrapper component around <For>
to extract my immutable structure into a JS array, but that would break some of the optimizations of immutable data.
Would you be open to a PR that either
Thanks
API example:
<switch expression={x}>
<case value={42}>
The Answer to the Ultimate Question of Life, the Universe, and Everything
</case>
<case value={41} fallThrough={true}>
close, but ...
</case>
<default>
wrong!
</default>
</choose>
By default every case
breaks to prevent the common and well known pitfall of this statement and to improve usability. Opt-in for fall-through behavior by explicitly specifiying fallThrough={true}
.
Default block is optional.
On Babel 7, plugins name on .babelrc became strict to single target: babel-plugin-{plugin-name}
{
"plugins": [ ...
"jsx-control-statements", // will find babel-plugin-jsx-control-statements only, and fails
"module:jsx-control-statements", // will find jsx-control-statements correctly.
]
}
just adding installation manual for Babel 7 is simple solution,
but i think renaming jsx-control-statements to babel-plugin-jsx-control-statements is more better.
How do you think about it?
The version of babel that you have in your package.json is outdated. It uses a module called chokidar
which requires the fsevents
module which will not build correctly on node 4.0.0
Lodash is only used a few times, and all the usages seem to be pretty simple use cases that could be replaced with native functions like Array.prototype.reduce and Array.prototype.forEach. There's no point adding the weight of the entire lodash library :)
I mean when I try this:
<For index="index" of={[100, 200, 300]}>
<span key={index}>{index}</span>
</For>
I get 100, 200, 300 and I'd like to get 1, 2, 3.
As far as I saw, when 'each' is missing the index key is passed as first param, so it takes the place of 'each'.
Is this a known issue?
I often find the need to create an else if. The syntax to do currently is rather ugly:
<If>
// Condition
</Else>
<If>
// First else if
</Else>
<If>
// Second else if
</Else>
// Else (first level)
</If>
</If
</If>
Hi
Thanks for this nice solution! It really helps to clear up the code.
I have a bit problem though when deploying the solution with CI which fails on the following message:
The plugin "jsx-control-statements/babel" didn't export a Plugin instance
My config is following:
var babelSettings = {
stage: 0,
plugins: ['jsx-control-statements/babel']
};
module.exports = {
entry: './entry',
module: {
loaders: [
{ test: /\.tsx?$/, loader: 'babel?' + JSON.stringify(babelSettings) + '!awesome-typescript', exclude: /node_modules|lib/ },
{ test: /\.jsx?$/, loader: 'babel', query: babelSettings, exclude: /node_modules/ },
{ test: /\.css$/, loader: 'style!css' },
{ test: /\.(png|jpe?g)(\?.*)?$/, loader: 'url?limit=8182' },
{ test: /\.(svg|ttf|woff|woff2|eot)(\?.*)?$/, loader: 'file' }
]
}
};
Any idea what can be wrong? Thanks!
Hi,
// before transformation
<If condition={ test }>
<span>Truth</span>
</If>
// after transformation
{ test ? <span>Truth</span> : null }
How does below code look like after transformation ?
Do you wrap these multiple statements into div?
// before transformation.
// using multiple child elements and / or expressions
<If condition={ true }>
one
{ "two" }
<span>three</span>
<span>four</span>
</If>
First of all, since this release would only add new features, but doesn't break anything, I think it should be release version 3.1.0.
Todos
<Choose>
I think, we should ship 3.1.0 when the ESLint-Plugin is ready.
Eg.
<Choose>
<When condition={this.props.when1}>
<span>WhenBlock1</span>
</When>
<When condition={this.props.when2}>
<span>WhenBlock2</span>
</When>
<Otherwise>
<span>OtherwiseBlock</span>
</Otherwise>
</Choose>
it('should render the first block when both conditions true', function () {
var rendered = util.render(Fixture, {when1: true, when2: true});
expect(rendered).to.match(util.matchTextWithinSpan('WhenBlock1'));
});
fails - it renders WhenBlock2
.
This seems to occur when I put a <Choose>
directly inside of a <When>
when there are no other components. It may occur in many other cases as well. Reverting to 3.1.2 fixed my issue. I can provide the whole code file if requested via email.
Thanks :D
Stack:
build modulesModuleBuildError: Module build failed: TypeError: /ephemeral0/work/2a4d4e3236001935/src/transpile/components/Datasets/View/List/MenuCell.js: Cannot read property 'attributes' of undefined
at exports.addKeyAttribute (/home/compellon/BuildAgent/work/2a4d4e3236001935/node_modules/jsx-control-statements/src/util/ast.js:86:22)
at Object.exports.getSanitizedExpressionForContent (/home/compellon/BuildAgent/work/2a4d4e3236001935/node_modules/jsx-control-statements/src/util/ast.js:113:7)
at /home/compellon/BuildAgent/work/2a4d4e3236001935/node_modules/jsx-control-statements/src/chooseStatement.js:30:49
at Array.reduceRight (native)
at getBlocks (/home/compellon/BuildAgent/work/2a4d4e3236001935/node_modules/jsx-control-statements/src/chooseStatement.js:17:25)
at /home/compellon/BuildAgent/work/2a4d4e3236001935/node_modules/jsx-control-statements/src/chooseStatement.js:65:18
at PluginPass.visitor.JSXElement (/home/compellon/BuildAgent/work/2a4d4e3236001935/node_modules/jsx-control-statements/src/index.js:19:26)
...
Code snippet:
<Choose>
<When condition={ dataset.status === 'COMPELTE' }>
<Choose>
<When condition={ preferences.app_mode }>
...
</When>
<Otherwise>
...
</Otherwise>
</Choose>
</When>
<Otherwise>
...
</Otherwise>
</Choose>
I find I have a common pattern of
<Choose>
<When condition={foo}>one-liner</When>
<Otherwise>
a lot of stuff
</Otherwise>
</Choose>
It kind of makes me want to just use a ternary; the control statements are neither succinct or clear/intent-expressive.
The one-liner in question will be a marker for “field not present”, an icon, a component, and in some cases even a single space.
An alternative which I think would make sense is something like:
<SomeControlTag condition={!foo} someprop="one-liner">
a lot of stuff
</SomeControlTag>
While having jsx in props looks weird, it's completely valid, and I've used it a couple of times with react-bootstrap stuff. I wouldn't recommend it for longer values, but it's fine for short stuff. So, you could in fact also have, someprop={<NotFound/>}
.
Now, assuming this is a good idea at all, what does the actual tag and prop look like?
My first instinct was to add this to :
<If condition={!foo} else="one-liner">
The problem should be obvious though… having “else” before “then” feels really odd. But I don't know if that objection is strong enough to justify loading the namespace with a new tag that does mostly the same thing as .
Here's a weird alternative, I don't like but I'll write it down anyway:
<If condition={foo} then="one-liner" else>
stuff
</If>
Anyway. Please discuss 😺
Since jstransform is no longer actively maintained, do you plan to drop support for it?
Would be much easier to maintain and extend.
There's a trick when using ternary expressions in JSX to have multiple adjacent elements by returning an array.
What I'd like to be able to see is for this...
<If condition={something}>
<p>Hello</p>
<p>World</p>
</If>
to get transpiled into this...
{something ?
[
<p>Hello</p>,
<p>World</p>
]
: ''
}
Of course it has the same issue as the <For>
block (cannot be the root of a component) but I say this would be a worthwhile addition to the transform.
Thanks you or this amazing set of controls that improve the readability of the code. Yet, I have a bit issue since I am a Typescript developer and I cannot make the choose statement to work, since it does not belong to the basic set of jsx statements. The If and For were no issue, since they are in capitals, thus only custom functions. What is the reason behind having "choose" and "when" with small letters? Would it be possible to use both, "Choose" and "choose"? Thanks!
Are there plans to support destructing the each
prop in the For
control statement? For example something like this:
<For each="{ firstName, lastName }" index="i" of={this.people}>
There's this one piece of code where I want to run a method once and bind its result locally. It looks more or less like this:
<For each="item" of={this.state.items}>
(bunch of stuff)
<Choose>
<When condition={this.isExpanded(item)}>
more stuff
</When>
<Otherwise>
{((summary) => <Choose>
<When condition={summary}>
stuff that uses the summary variable
</When>
<Otherwise>
also uses the variable
</Otherwise>
</Choose>)(this.getSummaryFields(item))}
</Otherwise>
</Choose>
</For>
Kind of makes my eyes bleed, and it's hard to see where the “variable” comes from.
(Sorry if the example is not as minimal as possible, but I wanted to illustrate why I want it to begin with; if it wasn't inside a <For>
I could just define it as a const
on top of the render method.)
Drawing from the ancient template languages of my young days…
<Let summary={this.getSummaryFields(item)}>
<Choose>
<When condition={summary}>
stuff that uses the summary variable
</When>
<Otherwise>
also uses the variable
</Otherwise>
</Choose>
</Let>
(Or you could call it <With>
or <Def>
or a number of other things. Not <Const>
though, that would make it look like it's doing something completely different.)
A possible objection is that using prop names this way is iffy, and I guess some are already uncomfortable with the each
and index
props in <For>
. An alternative, arguably more React-y:
<With const={{summary: this.getSummaryFields(item)}}>
It would expand of course to pretty much what I have on top, an in-place function, except no need for trickery with passing things as arguments.
// for clarity, allow me the poetic license of not expanding the other jsx
{(function() {
const summary = this.getSummaryFields(item);
return (
<Choose>
...
</Choose>
);
})()}
Known issue: expanding it that way means it's limited to containing exactly one child.
If the <If ...>
statement is not executed, then an empty <span>
is rendered, due to the fact that an empty string is returned: See https://github.com/AlexGilleran/jsx-control-statements/blob/master/jstransform.js#L79
Would be great if you would consider changing this empty string to null
, so that nothing is rendered.
Problematic cases are those where span tags are not allowed, e.g. within <tr>
ord <td>
. React throws warnings in this case...
Hi, this code works pretty well
<If condition={true}>
foo
</If>
But this not:
<Choose>
<When condition={true}>
bar
</When>
</Choose>
Unfortunately, there is no any related logs in the console, it just redirects me to the main page of my react app.
Since I wasn't happy with the choose syntax - hey, we should do better than JSP's! - I thought some more about how to realize the expected syntax. As it turns out Babel is just too powerful to not allow for that:
<If condition={true}>
...
</If>
<ElseIf condition={false}>
...
</ElseIf>
<Else>
...
</Else>
So today, after a fruitful discussion with a colleague, I learned how to traverse paths within Babel to make this happen. I will open a PR soon.
If you have something like this:
<If condition={aCondition}>
<span>Blah</span>
<If condition={anotherCondition}>
<span>Other Blah</span>
</If>
</If>
It seems to cause
warning.js:46 Warning: Each child in an array or iterator should have a unique "key" prop. Check the render method of `FeatureInfoSection`. See https://fb.me/react-warning-keys for more information.
Goes away if you do
<If condition={aCondition}>
<span>Blah</span>
<If condition={anotherCondition}>
<span key='otherblah'>Other Blah</span>
</If>
</If>
Looks like it'll be an annoying fix :(
Workaroundable by adding specific keys to the children of the <When>
element - see https://github.com/TerriaJS/terriajs/pull/2063/files.
Generates this warning:
warning.js:36 Warning: flattenChildren(...): Encountered two children with the same key, `5:0:$[object Object]`. Child keys must be unique; when two children share a key, only the first child will be used.
in div (created by MappablePreview)
in div (created by MappablePreview)
in div (created by MappablePreview)
in MappablePreview (created by DataPreview)
Two minor issues are not covered by the tests. Found them by analyzing istanbul's coverage report:
JSXElement
's are not affected by the transform<For>
and index
thoughWhen using JSX comments inside of a <Choose>
block during compilation with babel/webpack an error is presented that only <When>
and <Otherwise>
are valid.
The error displayed is: Only <Otherwise>
and <When>
are allowed child elements for <Choose>
!
The problem here is that these are not child elements, they are comments in JSX.
https://facebook.github.io/react/docs/jsx-in-depth.html#comments
Current Workaround
The workaround for this is to include them inside the markup for the <When>
or <Otherwise>
, but this results in poor form. The comment is for this specific block and it's condition - not it's containing elements. We are describing "this is the condition this block of code fulfills" rather than this is what these contain elements do.
Hopefully that is clear and we are enjoying using your control statements in our project so thank you for your work!
Any ideas of this could be used with Babel transpiler and Babelify ?
No one needs to download our tests unless they're developing it :)
At the moment, I need to declare types as globals to use jsx-control-statements
, e.g.
declare var If: any;
Otherwise, I will get Flow/ESLint error that If
variable is not defined.
Instead, it would be nice if I could simply import a dummy component from jsx-control-statements
, e.g.
import {If} from 'jsx-control-statements';
Consider this component
<Choose>
<When condition={tripsData.trips.length > 0}>
{console.log('this', this)} //this is OK
<For each="trip" of={tripsData.trips}>
{console.log('this', this)} //this is lost here
<Collapse isOpened key={`collapsed_ ${trip.id}`}>
<div className={cx('trip-container')}>
<Choose>
<When condition={trip.isOpen}>
<ExpandedTrip
cx={cx}
id={trip.id}
trip={trip}
isDebug={isDebug}
ticketCount={ticketCount}
onSelectSeat={this.onSelectSeat}
onTripClose={this.onTripClose}
firstStepActions={firstStepActions}
secondStepActions={secondStepActions}
buttonIsSpinning={buttonIsSpinning}
/>
</When>
<Otherwise>
<CollapsedTrip
cx={cx}
id={trip.id}
isDebug={isDebug}
trip={trip}
onTripClick={this.onTripClick}
/>
</Otherwise>
</Choose>
</div>
</Collapse>,
</For>
</When>
<Otherwise>
<div className={cx('trip')}>
Not found
</div>
</Otherwise>
</Choose>
In second console.log
context is lost.
I'm using gulp for js compiling and my task looks like that:
gulp.task('react', function() {
var b = browserify({
entries: './src/application.jsx',
extensions: ['.jsx'],
debug: true,
transform: [
jsxControlStatementsify,
babelify.configure({stage: 1}),
]
});
b.bundle().on('error', function(err) {
$.notify().write(err.toString());
this.emit("end");
}).pipe(source('application.js'))
.pipe(gulp.dest('js/'));
});
jsxControlStatementsify
is copy-pasted directly from your documentation on connecting it with gulp. The problem is that I'm using annotations:
@reactMixin.decorate(Reflux.connect(configurationStore))
export default class Sidebar extends React.Component {
Which works perfectly fine with babel, but with your transformer I'm getting the following error:
[12:34:08] gulp-notify: [Gulp notification] while transforming somepath\components\sidebar.jsx:Error: Parse Error: Line 6: Unexpected token ILLEGAL
Is there are a way to make them work together?
We should add linting to guarantee a consistent code style and get hints for common mistakes. Extending airbnb/eslint rules and adding own customizations is a common approach.
We should put some effort into making the documentation better:
is it work with enzime chai ?
Is it possible to get this plugin to work alongside babel-plugin-transform-react-inline-elements?
Hi, when the ternary operator is created with the null a ? <b/> : null
it creates havoc with some react approaches, such as with hot reloading or with react tools. Is it possible to somehow redefine the structure of the generated ternary statement to use undefined
or use && if no else exists?
This seems to work fine: a ? <b/> : undefined
Examples of problems:
Dose it use es6 or common js?
I'm using babel 6.2.1 with version 2.0.1 of the plugin. I'd like to do something along the lines of:
<If condition={foo}>
{'foo'}
</If>
Or with any expression that can be enclosed in {}
. But I get a nasty error instead:
Property consequent of ConditionalExpression expected node to be of a type ["Expression"] but instead got "JSXExpressionContainer" at Object.validate
Hi, would you consider adding a flag, in which you specify, whether curly brackets should be rendered or not? Consider this situation:
<For each="item" index="index" of={this.props.visibleItems}>
{ this.renderPracticalItem(Collections.Practicals.findOne(item.practicalId), this.props.schedule._id) }
</For>
In the for loop I need to render elements in which I do not want to render a containing element. The way to do it now is a bit hacky:
<For each="item" index="index" of={this.props.visibleItems}>
this.renderPracticalItem(Collections.Practicals.findOne(item.practicalId), this.props.schedule._id)
</For>
Currently it is more a technical than a semantic restriction to allow only one child element within control statements.
We could give up error checking:
if (ifBlock.length > 1) {
throwError(errors.MULTIPLE_CHILDREN, node, file);
} else if (ifBlock.length === 0) {
throwError(errors.NO_CHILDREN, node, file);
} else {
ifBlock = ifBlock[0];
}
Instead something along this lines could work:
ifBlock = ifBlock.length > 1 ? t.ArrayExpression(ifBlock) : ifBlock[0];
Consequences:
<span>
s are created, but that is how React workskey
s are required: should they be injected into each element automatically or left to the user (quite unintuitive)?Example:
<If condition={ true }>
<span {...{}}>Test</span>
<div />
</If>
Error message:
Cannot read property 'name' of undefined
Considering the nesting of <ElseIf>
and <Else>
they're just markers and cannot be indented automatically by IDE's. Correct indentation would look like this:
<If condition={x}>
IfBlock
<ElseIf condition={y} />
ElseIfBlock
<Else />
ElseBlock
</If>
So what about a choose statement (JSP style):
<choose>
<when condition={x}>
IfBlock
</when>
<when condition={y}>
ElseIfBlock
</when>
<otherwise>
Else
</otherwise>
</choose>
Or (misusing) switch style:
<switch>
<case condition={x}>
IfBlock
</case>
<case condition={y}>
ElseIfBlock
</case>
<default>
Else
</default>
</choose>
Maybe we should reserve the use of the switch statement instead of "abusing" the syntax. On the other hand we could also allow for an optional parameter for switch
which allows exactly that, e.g. expression
.
<switch expression={x}>
<case condition={12}>
IfBlock
</case>
<case condition={"Never mind"}>
ElseIfBlock
</case>
<default>
Else
</default>
</choose>
In any case I would tend to make otherwise / default
optional, so that users are not forced to switch between if/else and switch syntax. Implementation-wise we would simply make use of the ternary operator. @AlexGilleran what do you think?
Hi there,
I just love this package. It really improves readability of the jsx code when using control statements. However, the package does not seem to work anymore with babel-6.
Probably due to the babel.Transformer not being present any more. I will try to take a shot at fixing it. Any help would be greatly appreciated.
Just making this to keep track of it, I don't have a complete solution at this stage.
FlowType doesn't like JSX-Control-Statements in two ways - the first is that we're effectively using global types which is easily fixed with a type declaration:
// @flow
type ForType = ReactClass<{each: string, index: string, of: any}>;
declare var For: ForType;
But the problem is the "each" and "index" of the For won't be known to FlowType and it'll not only fail, but also not support type checking which sucks.
Best I have so far is to just declare those variables in the render function with the right type.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.